summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/getopt.erl533
-rw-r--r--src/rebar.erl56
-rw-r--r--src/rebar_abnfc_compiler.erl17
-rw-r--r--src/rebar_appups.erl10
-rw-r--r--src/rebar_asn1_compiler.erl20
-rw-r--r--src/rebar_base_compiler.erl2
-rw-r--r--src/rebar_cleaner.erl17
-rw-r--r--src/rebar_core.erl122
-rw-r--r--src/rebar_ct.erl55
-rw-r--r--src/rebar_deps.erl41
-rw-r--r--src/rebar_dia_compiler.erl20
-rw-r--r--src/rebar_edoc.erl11
-rw-r--r--src/rebar_erlc_compiler.erl43
-rw-r--r--src/rebar_erlydtl_compiler.erl33
-rw-r--r--src/rebar_escripter.erl27
-rw-r--r--src/rebar_eunit.erl45
-rw-r--r--src/rebar_lfe_compiler.erl11
-rw-r--r--src/rebar_neotoma_compiler.erl20
-rw-r--r--src/rebar_otp_app.erl35
-rw-r--r--src/rebar_port_compiler.erl31
-rw-r--r--src/rebar_protobuffs_compiler.erl21
-rw-r--r--src/rebar_qc.erl17
-rw-r--r--src/rebar_reltool.erl55
-rw-r--r--src/rebar_require_vsn.erl22
-rw-r--r--src/rebar_templater.erl48
-rw-r--r--src/rebar_upgrade.erl10
-rw-r--r--src/rebar_utils.erl4
-rw-r--r--src/rebar_xref.erl201
28 files changed, 1205 insertions, 322 deletions
diff --git a/src/getopt.erl b/src/getopt.erl
index 175b7a5..f9852fb 100644
--- a/src/getopt.erl
+++ b/src/getopt.erl
@@ -11,19 +11,11 @@
-module(getopt).
-author('juanjo@comellas.org').
--export([parse/2, usage/2, usage/3, usage/4]).
+-export([parse/2, usage/2, usage/3, usage/4, tokenize/1]).
+-export([usage_cmd_line/2]).
--export_type([arg_type/0,
- arg_value/0,
- arg_spec/0,
- simple_option/0,
- compound_option/0,
- option/0,
- option_spec/0]).
-
--define(TAB_LENGTH, 8).
-%% Indentation of the help messages in number of tabs.
--define(INDENTATION, 3).
+-define(LINE_LENGTH, 75).
+-define(MIN_USAGE_COMMAND_LINE_OPTION_LENGTH, 25).
%% Position of each field in the option specification tuple.
-define(OPT_NAME, 1).
@@ -33,42 +25,48 @@
-define(OPT_HELP, 5).
-define(IS_OPT_SPEC(Opt), (tuple_size(Opt) =:= ?OPT_HELP)).
-
+-define(IS_WHITESPACE(Char), ((Char) =:= $\s orelse (Char) =:= $\t orelse
+ (Char) =:= $\n orelse (Char) =:= $\r)).
%% Atom indicating the data type that an argument can be converted to.
--type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'.
+-type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'.
%% Data type that an argument can be converted to.
--type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
+-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
%% Argument specification.
--type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined.
+-type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined.
%% Option type and optional default argument.
--type simple_option() :: atom().
--type compound_option() :: {atom(), arg_value()}.
--type option() :: simple_option() | compound_option().
+-type simple_option() :: atom().
+-type compound_option() :: {atom(), arg_value()}.
+-type option() :: simple_option() | compound_option().
%% Command line option specification.
-type option_spec() :: {
- Name :: atom(),
- Short :: char() | undefined,
- Long :: string() | undefined,
- ArgSpec :: arg_spec(),
- Help :: string() | undefined
+ Name :: atom(),
+ Short :: char() | undefined,
+ Long :: string() | undefined,
+ ArgSpec :: arg_spec(),
+ Help :: string() | undefined
}.
%% Output streams
--type output_stream() :: 'standard_io' | 'standard_error'.
+-type output_stream() :: 'standard_io' | 'standard_error'.
+
+%% For internal use
+-type usage_line() :: {OptionText :: string(), HelpText :: string()}.
+-type usage_line_with_length() :: {OptionLength :: non_neg_integer(), OptionText :: string(), HelpText :: string()}.
+
+
+-export_type([arg_type/0, arg_value/0, arg_spec/0, simple_option/0, compound_option/0, option/0, option_spec/0]).
%% @doc Parse the command line options and arguments returning a list of tuples
%% and/or atoms using the Erlang convention for sending options to a
%% function.
-spec parse([option_spec()], string() | [string()]) ->
- {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}.
-parse(OptSpecList, CmdLine) ->
+ {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}.
+parse(OptSpecList, CmdLine) when is_list(CmdLine) ->
try
Args = if
- is_integer(hd(CmdLine)) ->
- string:tokens(CmdLine, " \t\n");
- true ->
- CmdLine
+ is_integer(hd(CmdLine)) -> tokenize(CmdLine);
+ true -> CmdLine
end,
parse(OptSpecList, [], [], 0, Args)
catch
@@ -78,7 +76,7 @@ parse(OptSpecList, CmdLine) ->
-spec parse([option_spec()], [option()], [string()], integer(), [string()]) ->
- {ok, {[option()], [string()]}}.
+ {ok, {[option()], [string()]}}.
%% Process the option terminator.
parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) ->
%% Any argument present after the terminator is not considered an option.
@@ -110,7 +108,7 @@ parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
%% --foo=bar Single option 'foo', argument "bar"
%% --foo bar Single option 'foo', argument "bar"
-spec parse_long_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) ->
- {ok, {[option()], [string()]}}.
+ {ok, {[option()], [string()]}}.
parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
case split_assigned_arg(OptArg) of
{Long, Arg} ->
@@ -197,7 +195,7 @@ parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Sh
%% -bcafoo Multiple options: 'b'; 'c'; 'a' with argument "foo"
%% -aaa Multiple repetitions of option 'a' (only valid for options with integer arguments)
-spec parse_short_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) ->
- {ok, {[option()], [string()]}}.
+ {ok, {[option()], [string()]}}.
parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg).
@@ -258,7 +256,7 @@ parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _S
%% Pos argument.
-spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false.
find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} = OptSpec | _Tail], 0) ->
- {value, OptSpec};
+ {value, OptSpec};
find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} | Tail], Pos) ->
find_non_option_arg(Tail, Pos - 1);
find_non_option_arg([_Head | Tail], Pos) ->
@@ -272,12 +270,12 @@ find_non_option_arg([], _Pos) ->
-spec append_default_options([option_spec()], [option()]) -> [option()].
append_default_options([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) ->
append_default_options(Tail,
- case lists:keymember(Name, 1, OptAcc) of
- false ->
- [{Name, DefaultArg} | OptAcc];
- _ ->
- OptAcc
- end);
+ case lists:keymember(Name, 1, OptAcc) of
+ false ->
+ [{Name, DefaultArg} | OptAcc];
+ _ ->
+ OptAcc
+ end);
%% For options with no default argument.
append_default_options([_Head | Tail], OptAcc) ->
append_default_options(Tail, OptAcc);
@@ -470,152 +468,375 @@ is_non_neg_float_arg([]) ->
%% arguments that are supported by the program.
-spec usage([option_spec()], string()) -> ok.
usage(OptSpecList, ProgramName) ->
- usage(OptSpecList, ProgramName, standard_error).
+ usage(OptSpecList, ProgramName, standard_error).
%% @doc Show a message on standard_error or standard_io indicating the command line options and
%% arguments that are supported by the program.
-spec usage([option_spec()], string(), output_stream() | string()) -> ok.
usage(OptSpecList, ProgramName, OutputStream) when is_atom(OutputStream) ->
- io:format(OutputStream, "Usage: ~s~s~n~n~s~n",
- [ProgramName, usage_cmd_line(OptSpecList), usage_options(OptSpecList)]);
+ io:format(OutputStream, "~s~n~n~s~n",
+ [usage_cmd_line(ProgramName, OptSpecList), usage_options(OptSpecList)]);
%% @doc Show a message on standard_error indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail argument
%% is a string that is added to the end of the usage command line.
usage(OptSpecList, ProgramName, CmdLineTail) ->
- usage(OptSpecList, ProgramName, CmdLineTail, standard_error).
+ usage(OptSpecList, ProgramName, CmdLineTail, standard_error).
%% @doc Show a message on standard_error or standard_io indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail argument
%% is a string that is added to the end of the usage command line.
--spec usage([option_spec()], string(), string(), output_stream() | [{string(), string()}]) -> ok.
+-spec usage([option_spec()], ProgramName :: string(), CmdLineTail :: string(), output_stream() | [{string(), string()}]) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) ->
- io:format(OutputStream, "Usage: ~s~s ~s~n~n~s~n",
- [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, usage_options(OptSpecList)]);
+ io:format(OutputStream, "~s~n~n~s~n",
+ [usage_cmd_line(ProgramName, OptSpecList, CmdLineTail), usage_options(OptSpecList)]);
%% @doc Show a message on standard_error indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail and OptionsTail
%% arguments are a string that is added to the end of the usage command line
%% and a list of tuples that are added to the end of the options' help lines.
usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) ->
- usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error).
+ usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error).
%% @doc Show a message on standard_error or standard_io indicating the command line options and
%% arguments that are supported by the program. The CmdLineTail and OptionsTail
%% arguments are a string that is added to the end of the usage command line
%% and a list of tuples that are added to the end of the options' help lines.
--spec usage([option_spec()], string(), string(), [{string(), string()}], output_stream()) -> ok.
+-spec usage([option_spec()], ProgramName :: string(), CmdLineTail :: string(),
+ [{OptionName :: string(), Help :: string()}], output_stream()) -> ok.
usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, OutputStream) ->
- UsageOptions = lists:foldl(
- fun ({Prefix, Help}, Acc) ->
- add_option_help(Prefix, Help, Acc)
- end, usage_options_reverse(OptSpecList, []), OptionsTail),
- io:format(OutputStream, "Usage: ~s~s ~s~n~n~s~n",
- [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail,
- lists:flatten(lists:reverse(UsageOptions))]).
-
-
-%% @doc Return a string with the syntax for the command line options and
-%% arguments.
--spec usage_cmd_line([option_spec()]) -> string().
-usage_cmd_line(OptSpecList) ->
- usage_cmd_line(OptSpecList, []).
-
-usage_cmd_line([{Name, Short, Long, ArgSpec, _Help} | Tail], Acc) ->
- CmdLine =
- case ArgSpec of
- undefined ->
- if
- %% For options with short form and no argument.
- Short =/= undefined ->
- [$\s, $[, $-, Short, $]];
- %% For options with only long form and no argument.
- Long =/= undefined ->
- [$\s, $[, $-, $-, Long, $]];
- true ->
- []
- end;
- _ ->
- if
- %% For options with short form and argument.
- Short =/= undefined ->
- [$\s, $[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]];
- %% For options with only long form and argument.
- Long =/= undefined ->
- [$\s, $[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]];
- %% For options with neither short nor long form and argument.
- true ->
- [$\s, $<, atom_to_list(Name), $>]
- end
- end,
- usage_cmd_line(Tail, [CmdLine | Acc]);
-usage_cmd_line([], Acc) ->
- lists:flatten(lists:reverse(Acc)).
-
-
-%% @doc Return a string with the help message for each of the options and
-%% arguments.
--spec usage_options([option_spec()]) -> string().
-usage_options(OptSpecList) ->
- lists:flatten(lists:reverse(usage_options_reverse(OptSpecList, []))).
-
-usage_options_reverse([{Name, Short, Long, _ArgSpec, Help} | Tail], Acc) ->
- Prefix =
- case Long of
- undefined ->
- case Short of
- %% Neither short nor long form (non-option argument).
- undefined ->
- [$<, atom_to_list(Name), $>];
- %% Only short form.
- _ ->
- [$-, Short]
- end;
- _ ->
- case Short of
- %% Only long form.
- undefined ->
- [$-, $- | Long];
- %% Both short and long form.
- _ ->
- [$-, Short, $,, $\s, $-, $- | Long]
- end
- end,
- usage_options_reverse(Tail, add_option_help(Prefix, Help, Acc));
-usage_options_reverse([], Acc) ->
- Acc.
-
-
-%% @doc Add the help message corresponding to an option specification to a list
-%% with the correct indentation.
--spec add_option_help(Prefix :: string(), Help :: string(), Acc :: string()) -> string().
-add_option_help(Prefix, Help, Acc) when is_list(Help), Help =/= [] ->
- FlatPrefix = lists:flatten(Prefix),
- case ((?INDENTATION * ?TAB_LENGTH) - 2 - length(FlatPrefix)) of
- TabSize when TabSize > 0 ->
- Tab = lists:duplicate(ceiling(TabSize / ?TAB_LENGTH), $\t),
- [[$\s, $\s, FlatPrefix, Tab, Help, $\n] | Acc];
- _ ->
- % The indentation for the option description is 3 tabs (i.e. 24 characters)
- % IMPORTANT: Change the number of tabs below if you change the
- % value of the INDENTATION macro.
- [[$\t, $\t, $\t, Help, $\n], [$\s, $\s, FlatPrefix, $\n] | Acc]
+ io:format(OutputStream, "~s~n~n~s~n",
+ [usage_cmd_line(ProgramName, OptSpecList, CmdLineTail), usage_options(OptSpecList, OptionsTail)]).
+
+
+-spec usage_cmd_line(ProgramName :: string(), [option_spec()]) -> iolist().
+usage_cmd_line(ProgramName, OptSpecList) ->
+ usage_cmd_line(ProgramName, OptSpecList, "").
+
+-spec usage_cmd_line(ProgramName :: string(), [option_spec()], CmdLineTail :: string()) -> iolist().
+usage_cmd_line(ProgramName, OptSpecList, CmdLineTail) ->
+ Prefix = "Usage: " ++ ProgramName,
+ PrefixLength = length(Prefix),
+ LineLength = line_length(),
+ %% Only align the command line options after the program name when there is
+ %% enough room to do so (i.e. at least 25 characters). If not, show the
+ %% command line options below the program name with a 2-character indentation.
+ if
+ (LineLength - PrefixLength) > ?MIN_USAGE_COMMAND_LINE_OPTION_LENGTH ->
+ Indentation = lists:duplicate(PrefixLength, $\s),
+ [FirstOptLine | OptLines] = usage_cmd_line_options(LineLength - PrefixLength, OptSpecList, CmdLineTail),
+ IndentedOptLines = [[Indentation | OptLine] || OptLine <- OptLines],
+ [Prefix, FirstOptLine | IndentedOptLines];
+ true ->
+ IndentedOptLines = [[" " | OptLine] || OptLine <- usage_cmd_line_options(LineLength, OptSpecList, CmdLineTail)],
+ [Prefix, $\n, IndentedOptLines]
+ end.
+
+
+%% @doc Return a list of the lines corresponding to the usage command line
+%% already wrapped according to the maximum MaxLineLength.
+-spec usage_cmd_line_options(MaxLineLength :: non_neg_integer(), [option_spec()], CmdLineTail :: string()) -> iolist().
+usage_cmd_line_options(MaxLineLength, OptSpecList, CmdLineTail) ->
+ usage_cmd_line_options(MaxLineLength, OptSpecList ++ string:tokens(CmdLineTail, " "), [], 0, []).
+
+usage_cmd_line_options(MaxLineLength, [OptSpec | Tail], LineAcc, LineAccLength, Acc) ->
+ Option = [$\s | lists:flatten(usage_cmd_line_option(OptSpec))],
+ OptionLength = length(Option),
+ %% We accumulate the options in LineAcc until its length is over the
+ %% maximum allowed line length. When that happens, we append the line in
+ %% LineAcc to the list with all the lines in the command line (Acc).
+ NewLineAccLength = LineAccLength + OptionLength,
+ if
+ NewLineAccLength < MaxLineLength ->
+ usage_cmd_line_options(MaxLineLength, Tail, [Option | LineAcc], NewLineAccLength, Acc);
+ true ->
+ usage_cmd_line_options(MaxLineLength, Tail, [Option], OptionLength + 1,
+ [lists:reverse([$\n | LineAcc]) | Acc])
+ end;
+usage_cmd_line_options(MaxLineLength, [], [_ | _] = LineAcc, _LineAccLength, Acc) ->
+ %% If there was a non-empty line in LineAcc when there are no more options
+ %% to process, we add it to the list of lines to return.
+ usage_cmd_line_options(MaxLineLength, [], [], 0, [lists:reverse(LineAcc) | Acc]);
+usage_cmd_line_options(_MaxLineLength, [], [], _LineAccLength, Acc) ->
+ lists:reverse(Acc).
+
+
+-spec usage_cmd_line_option(option_spec()) -> string().
+usage_cmd_line_option({_Name, Short, _Long, undefined, _Help}) when Short =/= undefined ->
+ %% For options with short form and no argument.
+ [$[, $-, Short, $]];
+usage_cmd_line_option({_Name, _Short, Long, undefined, _Help}) when Long =/= undefined ->
+ %% For options with only long form and no argument.
+ [$[, $-, $-, Long, $]];
+usage_cmd_line_option({_Name, _Short, _Long, undefined, _Help}) ->
+ [];
+usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_atom(ArgSpec) ->
+ %% For options with no default argument.
+ if
+ %% For options with short form and argument.
+ Short =/= undefined -> [$[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]];
+ %% For options with only long form and argument.
+ Long =/= undefined -> [$[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]];
+ %% For options with neither short nor long form and argument.
+ true -> [$[, $<, atom_to_list(Name), $>, $]]
end;
-add_option_help(_Opt, _Prefix, Acc) ->
- Acc.
+usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_tuple(ArgSpec) ->
+ %% For options with default argument.
+ if
+ %% For options with short form and default argument.
+ Short =/= undefined -> [$[, $-, Short, $\s, $[, $<, atom_to_list(Name), $>, $], $]];
+ %% For options with only long form and default argument.
+ Long =/= undefined -> [$[, $-, $-, Long, $\s, $[, $<, atom_to_list(Name), $>, $], $]];
+ %% For options with neither short nor long form and default argument.
+ true -> [$[, $<, atom_to_list(Name), $>, $]]
+ end;
+usage_cmd_line_option(Option) when is_list(Option) ->
+ %% For custom options that are added to the command line.
+ Option.
+
+%% @doc Return a list of help messages to print for each of the options and arguments.
+-spec usage_options([option_spec()]) -> [string()].
+usage_options(OptSpecList) ->
+ usage_options(OptSpecList, []).
+
+
+%% @doc Return a list of usage lines to print for each of the options and arguments.
+-spec usage_options([option_spec()], [{OptionName :: string(), Help :: string()}]) -> [string()].
+usage_options(OptSpecList, CustomHelp) ->
+ %% Add the usage lines corresponding to the option specifications.
+ {MaxOptionLength0, UsageLines0} = add_option_spec_help_lines(OptSpecList, 0, []),
+ %% Add the custom usage lines.
+ {MaxOptionLength, UsageLines} = add_custom_help_lines(CustomHelp, MaxOptionLength0, UsageLines0),
+ MaxLineLength = line_length(),
+ lists:reverse([format_usage_line(MaxOptionLength + 1, MaxLineLength, UsageLine) || UsageLine <- UsageLines]).
+
+
+-spec add_option_spec_help_lines([option_spec()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) ->
+ {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}.
+add_option_spec_help_lines([OptSpec | Tail], PrevMaxOptionLength, Acc) ->
+ OptionText = usage_option_text(OptSpec),
+ HelpText = usage_help_text(OptSpec),
+ {MaxOptionLength, ColsWithLength} = get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength),
+ add_option_spec_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]);
+add_option_spec_help_lines([], MaxOptionLength, Acc) ->
+ {MaxOptionLength, Acc}.
+
+
+-spec add_custom_help_lines([usage_line()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) ->
+ {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}.
+add_custom_help_lines([CustomCols | Tail], PrevMaxOptionLength, Acc) ->
+ {MaxOptionLength, ColsWithLength} = get_max_option_length(CustomCols, PrevMaxOptionLength),
+ add_custom_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]);
+add_custom_help_lines([], MaxOptionLength, Acc) ->
+ {MaxOptionLength, Acc}.
+
+
+-spec usage_option_text(option_spec()) -> string().
+usage_option_text({Name, undefined, undefined, _ArgSpec, _Help}) ->
+ %% Neither short nor long form (non-option argument).
+ "<" ++ atom_to_list(Name) ++ ">";
+usage_option_text({_Name, Short, undefined, _ArgSpec, _Help}) ->
+ %% Only short form.
+ [$-, Short];
+usage_option_text({_Name, undefined, Long, _ArgSpec, _Help}) ->
+ %% Only long form.
+ [$-, $- | Long];
+usage_option_text({_Name, Short, Long, _ArgSpec, _Help}) ->
+ %% Both short and long form.
+ [$-, Short, $,, $\s, $-, $- | Long].
+
+
+-spec usage_help_text(option_spec()) -> string().
+usage_help_text({_Name, _Short, _Long, {_ArgType, ArgValue}, [_ | _] = Help}) ->
+ Help ++ " [default: " ++ default_arg_value_to_string(ArgValue) ++ "]";
+usage_help_text({_Name, _Short, _Long, _ArgSpec, Help}) ->
+ Help.
+
+
+%% @doc Calculate the maximum width of the column that shows the option's short
+%% and long form.
+-spec get_max_option_length(usage_line(), PrevMaxOptionLength :: non_neg_integer()) ->
+ {MaxOptionLength :: non_neg_integer(), usage_line_with_length()}.
+get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength) ->
+ OptionLength = length(OptionText),
+ {erlang:max(OptionLength, PrevMaxOptionLength), {OptionLength, OptionText, HelpText}}.
+
+
+%% @doc Format the usage line that is shown for the options' usage. Each usage
+%% line has 2 columns. The first column shows the options in their short
+%% and long form. The second column shows the wrapped (if necessary) help
+%% text lines associated with each option. e.g.:
+%%
+%% -h, --host Database server host name or IP address; this is the
+%% hostname of the server where the database is running
+%% [default: localhost]
+%% -p, --port Database server port [default: 1000]
+%%
+-spec format_usage_line(MaxOptionLength :: non_neg_integer(), MaxLineLength :: non_neg_integer(),
+ usage_line_with_length()) -> iolist().
+format_usage_line(MaxOptionLength, MaxLineLength, {OptionLength, OptionText, [_ | _] = HelpText})
+ when MaxOptionLength < (MaxLineLength div 2) ->
+ %% If the width of the column where the options are shown is smaller than
+ %% half the width of a console line then we show the help text line aligned
+ %% next to its corresponding option, with a separation of at least 2
+ %% characters.
+ [Head | Tail] = wrap_text_line(MaxLineLength - MaxOptionLength - 3, HelpText),
+ FirstLineIndentation = lists:duplicate(MaxOptionLength - OptionLength + 1, $\s),
+ Indentation = [$\n | lists:duplicate(MaxOptionLength + 3, $\s)],
+ [" ", OptionText, FirstLineIndentation, Head,
+ [[Indentation, Line] || Line <- Tail], $\n];
+format_usage_line(_MaxOptionLength, MaxLineLength, {_OptionLength, OptionText, [_ | _] = HelpText}) ->
+ %% If the width of the first column is bigger than the width of a console
+ %% line, we show the help text on the next line with an indentation of 6
+ %% characters.
+ HelpLines = wrap_text_line(MaxLineLength - 6, HelpText),
+ [" ", OptionText, [["\n ", Line] || Line <- HelpLines], $\n];
+format_usage_line(_MaxOptionLength, _MaxLineLength, {_OptionLength, OptionText, _HelpText}) ->
+ [" ", OptionText, $\n].
+
+
+%% @doc Wrap a text line converting it into several text lines so that the
+%% length of each one of them is never over HelpLength characters.
+-spec wrap_text_line(Length :: non_neg_integer(), Text :: string()) -> [string()].
+wrap_text_line(Length, Text) ->
+ wrap_text_line(Length, Text, [], 0, []).
+
+wrap_text_line(Length, [Char | Tail], Acc, Count, CurrentLineAcc) when Count < Length ->
+ wrap_text_line(Length, Tail, Acc, Count + 1, [Char | CurrentLineAcc]);
+wrap_text_line(Length, [_ | _] = Help, Acc, Count, CurrentLineAcc) ->
+ %% Look for the first whitespace character in the current (reversed) line
+ %% buffer to get a wrapped line. If there is no whitespace just cut the
+ %% line at the position corresponding to the maximum length.
+ {NextLineAcc, WrappedLine} = case string:cspan(CurrentLineAcc, " \t") of
+ WhitespacePos when WhitespacePos < Count ->
+ lists:split(WhitespacePos, CurrentLineAcc);
+ _ ->
+ {[], CurrentLineAcc}
+ end,
+ wrap_text_line(Length, Help, [lists:reverse(WrappedLine) | Acc], length(NextLineAcc), NextLineAcc);
+wrap_text_line(_Length, [], Acc, _Count, [_ | _] = CurrentLineAcc) ->
+ %% If there was a non-empty line when we reached the buffer, add it to the accumulator
+ lists:reverse([lists:reverse(CurrentLineAcc) | Acc]);
+wrap_text_line(_Length, [], Acc, _Count, _CurrentLineAcc) ->
+ lists:reverse(Acc).
+
+
+default_arg_value_to_string(Value) when is_atom(Value) ->
+ atom_to_list(Value);
+default_arg_value_to_string(Value) when is_binary(Value) ->
+ binary_to_list(Value);
+default_arg_value_to_string(Value) when is_integer(Value) ->
+ integer_to_list(Value);
+default_arg_value_to_string(Value) when is_float(Value) ->
+ float_to_list(Value);
+default_arg_value_to_string(Value) ->
+ Value.
+
+
+%% @doc Tokenize a command line string with support for single and double
+%% quoted arguments (needed for arguments that have embedded whitespace).
+%% The function also supports the expansion of environment variables in
+%% both the Unix (${VAR}; $VAR) and Windows (%VAR%) formats. It does NOT
+%% support wildcard expansion of paths.
+-spec tokenize(CmdLine :: string()) -> [nonempty_string()].
+tokenize(CmdLine) ->
+ tokenize(CmdLine, [], []).
+
+-spec tokenize(CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()].
+tokenize([Sep | Tail], Acc, ArgAcc) when ?IS_WHITESPACE(Sep) ->
+ NewAcc = case ArgAcc of
+ [_ | _] ->
+ %% Found separator: add to the list of arguments.
+ [lists:reverse(ArgAcc) | Acc];
+ [] ->
+ %% Found separator with no accumulated argument; discard it.
+ Acc
+ end,
+ tokenize(Tail, NewAcc, []);
+tokenize([QuotationMark | Tail], Acc, ArgAcc) when QuotationMark =:= $"; QuotationMark =:= $' ->
+ %% Quoted argument (might contain spaces, tabs, etc.)
+ tokenize_quoted_arg(QuotationMark, Tail, Acc, ArgAcc);
+tokenize([Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% ->
+ %% Unix and Windows environment variable expansion: ${VAR}; $VAR; %VAR%
+ {NewCmdLine, Var} = expand_env_var(CmdLine),
+ tokenize(NewCmdLine, Acc, lists:reverse(Var, ArgAcc));
+tokenize([$\\, Char | Tail], Acc, ArgAcc) ->
+ %% Escaped char.
+ tokenize(Tail, Acc, [Char | ArgAcc]);
+tokenize([Char | Tail], Acc, ArgAcc) ->
+ tokenize(Tail, Acc, [Char | ArgAcc]);
+tokenize([], Acc, []) ->
+ lists:reverse(Acc);
+tokenize([], Acc, ArgAcc) ->
+ lists:reverse([lists:reverse(ArgAcc) | Acc]).
+
+-spec tokenize_quoted_arg(QuotationMark :: char(), CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()].
+tokenize_quoted_arg(QuotationMark, [QuotationMark | Tail], Acc, ArgAcc) ->
+ %% End of quoted argument
+ tokenize(Tail, Acc, ArgAcc);
+tokenize_quoted_arg(QuotationMark, [$\\, Char | Tail], Acc, ArgAcc) ->
+ %% Escaped char.
+ tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]);
+tokenize_quoted_arg($" = QuotationMark, [Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% ->
+ %% Unix and Windows environment variable expansion (only for double-quoted arguments): ${VAR}; $VAR; %VAR%
+ {NewCmdLine, Var} = expand_env_var(CmdLine),
+ tokenize_quoted_arg(QuotationMark, NewCmdLine, Acc, lists:reverse(Var, ArgAcc));
+tokenize_quoted_arg(QuotationMark, [Char | Tail], Acc, ArgAcc) ->
+ tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]);
+tokenize_quoted_arg(_QuotationMark, CmdLine, Acc, ArgAcc) ->
+ tokenize(CmdLine, Acc, ArgAcc).
+
+
+-spec expand_env_var(CmdLine :: nonempty_string()) -> {string(), string()}.
+expand_env_var(CmdLine) ->
+ case CmdLine of
+ "${" ++ Tail ->
+ expand_env_var("${", $}, Tail, []);
+ "$" ++ Tail ->
+ expand_env_var("$", Tail, []);
+ "%" ++ Tail ->
+ expand_env_var("%", $%, Tail, [])
+ end.
+
+-spec expand_env_var(Prefix :: string(), EndMark :: char(), CmdLine :: string(), Acc :: string()) -> {string(), string()}.
+expand_env_var(Prefix, EndMark, [Char | Tail], Acc)
+ when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse
+ (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) ->
+ expand_env_var(Prefix, EndMark, Tail, [Char | Acc]);
+expand_env_var(Prefix, EndMark, [EndMark | Tail], Acc) ->
+ {Tail, get_env_var(Prefix, [EndMark], Acc)};
+expand_env_var(Prefix, _EndMark, CmdLine, Acc) ->
+ {CmdLine, Prefix ++ lists:reverse(Acc)}.
+
+
+-spec expand_env_var(Prefix :: string(), CmdLine :: string(), Acc :: string()) -> {string(), string()}.
+expand_env_var(Prefix, [Char | Tail], Acc)
+ when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse
+ (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) ->
+ expand_env_var(Prefix, Tail, [Char | Acc]);
+expand_env_var(Prefix, CmdLine, Acc) ->
+ {CmdLine, get_env_var(Prefix, "", Acc)}.
+
+
+-spec get_env_var(Prefix :: string(), Suffix :: string(), Acc :: string()) -> string().
+get_env_var(Prefix, Suffix, [_ | _] = Acc) ->
+ Name = lists:reverse(Acc),
+ %% Only expand valid/existing variables.
+ case os:getenv(Name) of
+ false -> Prefix ++ Name ++ Suffix;
+ Value -> Value
+ end;
+get_env_var(Prefix, Suffix, []) ->
+ Prefix ++ Suffix.
-%% @doc Return the smallest integral value not less than the argument.
--spec ceiling(float()) -> integer().
-ceiling(X) ->
- T = erlang:trunc(X),
- case (X - T) of
- % Neg when Neg < 0 ->
- % T;
- Pos when Pos > 0 ->
- T + 1;
+-spec line_length() -> non_neg_integer().
+line_length() ->
+ case io:columns() of
+ {ok, Columns} when Columns < ?LINE_LENGTH ->
+ Columns - 1;
_ ->
- T
+ ?LINE_LENGTH
end.
diff --git a/src/rebar.erl b/src/rebar.erl
index c872ade..ded5ebe 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -76,8 +76,18 @@ run(BaseConfig, Commands) ->
%% Internal functions
%% ====================================================================
+run(["help"|RawCmds]) when RawCmds =/= [] ->
+ ok = load_rebar_app(),
+ Cmds = unabbreviate_command_names(RawCmds),
+ Args = parse_args(Cmds),
+ BaseConfig = init_config(Args),
+ {BaseConfig1, _} = save_options(BaseConfig, Args),
+ BaseConfig2 = init_config1(BaseConfig1),
+ rebar_core:help(BaseConfig2, [list_to_atom(C) || C <- Cmds]);
run(["help"]) ->
help();
+run(["info"|_]) ->
+ help();
run(["version"]) ->
ok = load_rebar_app(),
%% Display vsn and build time info
@@ -138,6 +148,16 @@ init_config({Options, _NonOptArgs}) ->
%% Initialize vsn cache
rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()).
+init_config1(BaseConfig) ->
+ %% Determine the location of the rebar executable; important for pulling
+ %% resources out of the escript
+ ScriptName = filename:absname(escript:script_name()),
+ BaseConfig1 = rebar_config:set_xconf(BaseConfig, escript, ScriptName),
+ ?DEBUG("Rebar location: ~p\n", [ScriptName]),
+ %% Note the top-level directory for reference
+ AbsCwd = filename:absname(rebar_utils:get_cwd()),
+ rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd).
+
run_aux(BaseConfig, Commands) ->
%% Make sure crypto is running
case crypto:start() of
@@ -148,18 +168,10 @@ run_aux(BaseConfig, Commands) ->
%% Convert command strings to atoms
CommandAtoms = [list_to_atom(C) || C <- Commands],
- %% Determine the location of the rebar executable; important for pulling
- %% resources out of the escript
- ScriptName = filename:absname(escript:script_name()),
- BaseConfig1 = rebar_config:set_xconf(BaseConfig, escript, ScriptName),
- ?DEBUG("Rebar location: ~p\n", [ScriptName]),
-
- %% Note the top-level directory for reference
- AbsCwd = filename:absname(rebar_utils:get_cwd()),
- BaseConfig2 = rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd),
+ BaseConfig1 = init_config1(BaseConfig),
%% Process each command, resetting any state between each one
- rebar_core:process_commands(CommandAtoms, BaseConfig2).
+ rebar_core:process_commands(CommandAtoms, BaseConfig1).
%%
%% print help/usage string
@@ -169,7 +181,29 @@ help() ->
getopt:usage(OptSpecList, "rebar",
"[var=value,...] <command,...>",
[{"var=value", "rebar global variables (e.g. force=1)"},
- {"command", "Command to run (e.g. compile)"}]).
+ {"command", "Command to run (e.g. compile)"}]),
+ ?CONSOLE(
+ "Core rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {lib_dirs, []},
+ {sub_dirs, ["dir1", "dir2"]},
+ {plugins, [plugin1, plugin2]},
+ {plugin_dir, "some_other_directory"},
+ {pre_hooks, [{clean, "./prepare_package_files.sh"},
+ {"linux", compile, "c_src/build_linux.sh"},
+ {compile, "escript generate_headers"},
+ {compile, "escript check_headers"}]},
+ {post_hooks, [{clean, "touch file1.out"},
+ {"freebsd", compile, "c_src/freebsd_tweaks.sh"},
+ {eunit, "touch file2.out"},
+ {compile, "touch postcompile.out"}]}
+ ]).
%%
%% Parse command line arguments using getopt and also filtering out any
diff --git a/src/rebar_abnfc_compiler.erl b/src/rebar_abnfc_compiler.erl
index 0e6749a..37731b5 100644
--- a/src/rebar_abnfc_compiler.erl
+++ b/src/rebar_abnfc_compiler.erl
@@ -47,6 +47,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -62,11 +65,23 @@ compile(Config, _AppFile) ->
option(module_ext, DtlOpts) ++ ".erl",
fun compile_abnfc/3).
-
%% ===================================================================
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ ?CONSOLE(
+ "Build ABNF (*.abnf) sources.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {abnfc_opts, [{doc_root, "src"},
+ {out_dir, "src"},
+ {source_ext, ".abnfc"},
+ {module_ext, ""}]}
+ ]).
+
abnfc_opts(Config) ->
rebar_config:get(Config, abnfc_opts, []).
diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl
index 0aeccb6..722f161 100644
--- a/src/rebar_appups.erl
+++ b/src/rebar_appups.erl
@@ -31,6 +31,9 @@
-export(['generate-appups'/2]).
+%% for internal use only
+-export([info/2]).
+
-define(APPUPFILEFORMAT, "%% appup generated for ~p by rebar (~p)~n"
"{~p, [{~p, ~p}], [{~p, []}]}.~n").
@@ -82,6 +85,13 @@
%% Internal functions
%% ===================================================================
+info(help, 'generate-appups') ->
+ ?CONSOLE("Generate appup files.~n"
+ "~n"
+ "Valid command line options:~n"
+ " previous_release=path~n",
+ []).
+
get_apps(Name, OldVerPath, NewVerPath) ->
OldApps = rebar_rel_utils:get_rel_apps(Name, OldVerPath),
?DEBUG("Old Version Apps: ~p~n", [OldApps]),
diff --git a/src/rebar_asn1_compiler.erl b/src/rebar_asn1_compiler.erl
index d93a2e8..25e3fd3 100644
--- a/src/rebar_asn1_compiler.erl
+++ b/src/rebar_asn1_compiler.erl
@@ -30,6 +30,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -48,6 +51,23 @@ clean(_Config, _AppFile) ->
ok = rebar_file_utils:delete_each(GeneratedFiles),
ok.
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+ info_help("Build ASN.1 (*.asn1) sources");
+info(help, clean) ->
+ info_help("Delete ASN.1 (*.asn1) results").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " {asn1_opts, []} (see asn1ct:compile/2 documentation)~n",
+ [Description]).
+
-spec compile_asn1(file:filename(), file:filename(),
rebar_config:config()) -> ok.
compile_asn1(Source, Target, Config) ->
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index 260cdaf..a0dec30 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -226,6 +226,8 @@ format_warnings(Config, Source, Warnings, Opts) ->
maybe_report([{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}]) ->
maybe_report(ErrorsAndWarnings);
+maybe_report([{error, E}, {source, S}]) ->
+ report(["unexpected error compiling " ++ S, io_lib:fwrite("~n~p~n", [E])]);
maybe_report({error, Es, Ws}) ->
report(Es),
report(Ws);
diff --git a/src/rebar_cleaner.erl b/src/rebar_cleaner.erl
index 9ddeb8a..7a762f5 100644
--- a/src/rebar_cleaner.erl
+++ b/src/rebar_cleaner.erl
@@ -28,6 +28,9 @@
-export([clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -37,3 +40,17 @@ clean(Config, _AppFile) ->
%% Get a list of files to delete from config and remove them
FilesToClean = rebar_config:get(Config, clean_files, []),
lists:foreach(fun (F) -> rebar_file_utils:rm_rf(F) end, FilesToClean).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, clean) ->
+ ?CONSOLE(
+ "Delete list of files.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {clean_files, ["file", "file2"]}
+ ]).
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index 5396dd5..fcfa62a 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -26,7 +26,7 @@
%% -------------------------------------------------------------------
-module(rebar_core).
--export([process_commands/2]).
+-export([process_commands/2, help/2]).
-include("rebar.hrl").
@@ -34,6 +34,35 @@
%% Internal functions
%% ===================================================================
+help(ParentConfig, Commands) ->
+ %% get all core modules
+ {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules),
+ {ok, RawCoreModules} = application:get_env(rebar, modules),
+ AppDirModules = proplists:get_value(app_dir, RawCoreModules),
+ RelDirModules = proplists:get_value(rel_dir, RawCoreModules),
+ CoreModules = AnyDirModules ++ AppDirModules ++ RelDirModules,
+
+ %% get plugin modules
+ Predirs = [],
+ Dir = rebar_utils:get_cwd(),
+ PredirsAssoc = remember_cwd_predirs(Dir, Predirs),
+ Config = maybe_load_local_config(Dir, ParentConfig),
+ {ok, PluginModules} = plugin_modules(Config, PredirsAssoc),
+
+ AllModules = CoreModules ++ PluginModules,
+
+ lists:foreach(
+ fun(Cmd) ->
+ ?CONSOLE("==> help ~p~n~n", [Cmd]),
+ CmdModules = select_modules(AllModules, Cmd, []),
+ Modules = select_modules(CmdModules, info, []),
+ lists:foreach(fun(M) ->
+ ?CONSOLE("=== ~p:~p ===~n", [M, Cmd]),
+ M:info(help, Cmd),
+ ?CONSOLE("~n", [])
+ end, Modules)
+ end, Commands).
+
process_commands([], ParentConfig) ->
AbortTrapped = rebar_config:get_xconf(ParentConfig, abort_trapped, false),
case {get_operations(ParentConfig), AbortTrapped} of
@@ -110,21 +139,21 @@ process_dir(Dir, ParentConfig, Command, DirSet) ->
{ok, AvailModuleSets} = application:get_env(rebar, modules),
ModuleSet = choose_module_set(AvailModuleSets, Dir),
skip_or_process_dir(ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet)
+ Dir, Command, DirSet)
end.
skip_or_process_dir({[], undefined}=ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
process_dir1(Dir, Command, DirSet, Config, CurrentCodePath, ModuleSet);
skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
case lists:suffix(".app.src", ModuleSetFile)
orelse lists:suffix(".app", ModuleSetFile) of
true ->
%% .app or .app.src file, check if is_skipped_app
skip_or_process_dir1(ModuleSetFile, ModuleSet,
- Config, CurrentCodePath, Dir,
- Command, DirSet);
+ Config, CurrentCodePath, Dir,
+ Command, DirSet);
false ->
%% not an app dir, no need to consider apps=/skip_apps=
process_dir1(Dir, Command, DirSet, Config,
@@ -132,7 +161,7 @@ skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
end.
skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
case rebar_app_utils:is_skipped_app(Config, AppFile) of
{Config1, {true, SkippedApp}} ->
?DEBUG("Skipping app: ~p~n", [SkippedApp]),
@@ -158,11 +187,12 @@ process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
{Config1, Predirs} = acc_modules(Modules, preprocess, Config0,
ModuleSetFile),
- SubdirAssoc = remember_cwd_subdir(Dir, Predirs),
+ %% Remember associated pre-dirs (used for plugin lookup)
+ PredirsAssoc = remember_cwd_predirs(Dir, Predirs),
%% Get the list of plug-in modules from rebar.config. These
%% modules may participate in preprocess and postprocess.
- {ok, PluginModules} = plugin_modules(Config1, SubdirAssoc),
+ {ok, PluginModules} = plugin_modules(Config1, PredirsAssoc),
{Config2, PluginPredirs} = acc_modules(PluginModules, preprocess,
Config1, ModuleSetFile),
@@ -224,7 +254,7 @@ process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
%% Return the updated {config, dirset} as result
Res.
-remember_cwd_subdir(Cwd, Subdirs) ->
+remember_cwd_predirs(Cwd, Predirs) ->
Store = fun(Dir, Dict) ->
case dict:find(Dir, Dict) of
error ->
@@ -235,11 +265,10 @@ remember_cwd_subdir(Cwd, Subdirs) ->
?ABORT("Internal consistency assertion failed.~n"
"sub_dir ~s already associated with ~s.~n"
"Duplicate sub_dirs or deps entries?",
- [Dir, Existing]),
- Dict
+ [Dir, Existing])
end
end,
- lists:foldl(Store, dict:new(), Subdirs).
+ lists:foldl(Store, dict:new(), Predirs).
maybe_load_local_config(Dir, ParentConfig) ->
%% We need to ensure we don't overwrite custom
@@ -383,7 +412,10 @@ expand_lib_dirs([], _Root, Acc) ->
Acc;
expand_lib_dirs([Dir | Rest], Root, Acc) ->
Apps = filelib:wildcard(filename:join([Dir, "*", "ebin"])),
- FqApps = [filename:join([Root, A]) || A <- Apps],
+ FqApps = case filename:pathtype(Dir) of
+ absolute -> Apps;
+ _ -> [filename:join([Root, A]) || A <- Apps]
+ end,
expand_lib_dirs(Rest, Root, Acc ++ FqApps).
@@ -459,9 +491,9 @@ acc_modules([Module | Rest], Command, Config, File, Acc) ->
%%
%% Return a flat list of rebar plugin modules.
%%
-plugin_modules(Config, SubdirAssoc) ->
+plugin_modules(Config, PredirsAssoc) ->
Modules = lists:flatten(rebar_config:get_all(Config, plugins)),
- plugin_modules(Config, SubdirAssoc, ulist(Modules)).
+ plugin_modules(Config, PredirsAssoc, ulist(Modules)).
ulist(L) ->
ulist(L, []).
@@ -476,16 +508,16 @@ ulist([H | T], Acc) ->
ulist(T, [H | Acc])
end.
-plugin_modules(_Config, _SubdirAssoc, []) ->
+plugin_modules(_Config, _PredirsAssoc, []) ->
{ok, []};
-plugin_modules(Config, SubdirAssoc, Modules) ->
+plugin_modules(Config, PredirsAssoc, Modules) ->
FoundModules = [M || M <- Modules, code:which(M) =/= non_existing],
- plugin_modules(Config, SubdirAssoc, FoundModules, Modules -- FoundModules).
+ plugin_modules(Config, PredirsAssoc, FoundModules, Modules -- FoundModules).
-plugin_modules(_Config, _SubdirAssoc, FoundModules, []) ->
+plugin_modules(_Config, _PredirsAssoc, FoundModules, []) ->
{ok, FoundModules};
-plugin_modules(Config, SubdirAssoc, FoundModules, MissingModules) ->
- {Loaded, NotLoaded} = load_plugin_modules(Config, SubdirAssoc,
+plugin_modules(Config, PredirsAssoc, FoundModules, MissingModules) ->
+ {Loaded, NotLoaded} = load_plugin_modules(Config, PredirsAssoc,
MissingModules),
AllViablePlugins = FoundModules ++ Loaded,
case NotLoaded =/= [] of
@@ -499,38 +531,50 @@ plugin_modules(Config, SubdirAssoc, FoundModules, MissingModules) ->
end,
{ok, AllViablePlugins}.
-load_plugin_modules(Config, SubdirAssoc, Modules) ->
+load_plugin_modules(Config, PredirsAssoc, Modules) ->
Cwd = rebar_utils:get_cwd(),
- PluginDir = case rebar_config:get_local(Config, plugin_dir, undefined) of
- undefined ->
- filename:join(Cwd, "plugins");
- Dir ->
- Dir
- end,
+ PluginDirs = get_all_plugin_dirs(Config, Cwd, PredirsAssoc),
%% Find relevant sources in base_dir and plugin_dir
Erls = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"),
RE = "^" ++ Erls ++ "\$",
- BaseDir = get_plugin_base_dir(Cwd, SubdirAssoc),
%% If a plugin is found both in base_dir and plugin_dir, the clash
%% will provoke an error and we'll abort.
- Sources = rebar_utils:find_files(PluginDir, RE, false)
- ++ rebar_utils:find_files(BaseDir, RE, false),
+ Sources = [rebar_utils:find_files(PD, RE, false) || PD <- PluginDirs],
%% Compile and load plugins
- Loaded = [load_plugin(Src) || Src <- Sources],
+ Loaded = [load_plugin(Src) || Src <- lists:append(Sources)],
FilterMissing = is_missing_plugin(Loaded),
NotLoaded = [V || V <- Modules, FilterMissing(V)],
{Loaded, NotLoaded}.
-get_plugin_base_dir(Cwd, SubdirAssoc) ->
- case dict:find(Cwd, SubdirAssoc) of
- {ok, BaseDir} ->
- BaseDir;
- error ->
- Cwd
+get_all_plugin_dirs(Config, Cwd, PredirsAssoc) ->
+ get_plugin_dir(Config, Cwd) ++ get_base_plugin_dirs(Cwd, PredirsAssoc).
+
+get_plugin_dir(Config, Cwd) ->
+ case rebar_config:get_local(Config, plugin_dir, undefined) of
+ undefined ->
+ %% Plugin can be in the project's "plugins" folder
+ [filename:join(Cwd, "plugins")];
+ Dir ->
+ [Dir]
end.
+%% We also want to include this case:
+%% Plugin can be in "plugins" directory of the plugin base directory.
+%% For example, Cwd depends on Plugin, and deps/Plugin/plugins/Plugin.erl
+%% is the plugin.
+get_base_plugin_dirs(Cwd, PredirsAssoc) ->
+ [filename:join(Dir, "plugins") ||
+ Dir <- get_plugin_base_dirs(Cwd, PredirsAssoc)].
+
+%% @doc PredirsAssoc is a dictionary of plugindir -> 'parent' pairs
+%% 'parent' in this case depends on plugin; therefore we have to give
+%% all plugins that Cwd ('parent' in this case) depends on.
+get_plugin_base_dirs(Cwd, PredirsAssoc) ->
+ [PluginDir || {PluginDir, Master} <- dict:to_list(PredirsAssoc),
+ Master =:= Cwd].
+
is_missing_plugin(Loaded) ->
fun(Mod) -> not lists:member(Mod, Loaded) end.
diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl
index 749d025..66b9d76 100644
--- a/src/rebar_ct.erl
+++ b/src/rebar_ct.erl
@@ -39,6 +39,9 @@
-export([ct/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -53,6 +56,26 @@ ct(Config, File) ->
%% ===================================================================
%% Internal functions
%% ===================================================================
+
+info(help, ct) ->
+ ?CONSOLE(
+ "Run common_test suites.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ "Valid command line options:~n"
+ " suites=foo,bar - run <test>/foo_SUITE and <test>/bar_SUITE~n"
+ " case=\"mycase\" - run individual test case foo_SUITE:mycase~n",
+ [
+ {ct_dir, "itest"},
+ {ct_log_dir, "test/logs"},
+ {ct_extra_params, "-boot start_sasl -s myapp"},
+ {ct_use_short_names, true}
+ ]).
+
run_test_if_present(TestDir, LogDir, Config, File) ->
case filelib:is_dir(TestDir) of
false ->
@@ -85,8 +108,17 @@ run_test(TestDir, LogDir, Config, _File) ->
" 2>&1 | tee -a " ++ RawLog
end,
- rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]),
- check_log(Config, RawLog).
+ ShOpts = [{env,[{"TESTDIR", TestDir}]}, return_on_error],
+ case rebar_utils:sh(Cmd ++ Output, ShOpts) of
+ {ok,_} ->
+ %% in older versions of ct_run, this could have been a failure
+ %% that returned a non-0 code. Check for that!
+ check_success_log(Config, RawLog);
+ {error,Res} ->
+ %% In newer ct_run versions, this may be a sign of a good compile
+ %% that failed cases. In older version, it's a worse error.
+ check_fail_log(Config, RawLog, Cmd ++ Output, Res)
+ end.
clear_log(LogDir, RawLog) ->
case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
@@ -101,7 +133,21 @@ clear_log(LogDir, RawLog) ->
%% calling ct with erl does not return non-zero on failure - have to check
%% log results
-check_log(Config, RawLog) ->
+check_success_log(Config, RawLog) ->
+ check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
+
+-type err_handler() :: fun((string()) -> no_return()).
+-spec failure_logger(string(), {integer(), string()}) -> err_handler().
+failure_logger(Command, {Rc, Output}) ->
+ fun(_Msg) ->
+ ?ABORT("~s failed with error: ~w and output:~n~s~n",
+ [Command, Rc, Output])
+ end.
+
+check_fail_log(Config, RawLog, Command, Result) ->
+ check_log(Config, RawLog, failure_logger(Command, Result)).
+
+check_log(Config,RawLog,Fun) ->
{ok, Msg} =
rebar_utils:sh("grep -e 'TEST COMPLETE' -e '{error,make_failed}' "
++ RawLog, [{use_stdout, false}]),
@@ -119,9 +165,10 @@ check_log(Config, RawLog) ->
?FAIL;
true ->
- ?CONSOLE("DONE.\n~s\n", [Msg])
+ Fun(Msg)
end.
+
%% Show the log if it hasn't already been shown because verbose was on
show_log(Config, RawLog) ->
?CONSOLE("Showing log\n", []),
diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl
index 074e929..9f9fc5d 100644
--- a/src/rebar_deps.erl
+++ b/src/rebar_deps.erl
@@ -38,6 +38,8 @@
'delete-deps'/2,
'list-deps'/2]).
+%% for internal use only
+-export([info/2]).
-record(dep, { dir,
app,
@@ -203,12 +205,47 @@ do_check_deps(Config) ->
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Display to be fetched dependencies");
+info(help, 'check-deps') ->
+ info_help("Display to be fetched dependencies");
+info(help, 'get-deps') ->
+ info_help("Fetch dependencies");
+info(help, 'update-deps') ->
+ info_help("Update fetched dependencies");
+info(help, 'delete-deps') ->
+ info_help("Delete fetched dependencies");
+info(help, 'list-deps') ->
+ info_help("List dependencies").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ "Valid command line options:~n"
+ " deps_dir=\"deps\" (override default or rebar.config deps_dir)~n",
+ [
+ Description,
+ {deps_dir, "deps"},
+ {deps, [application_name,
+ {application_name, "1.0.*"},
+ {application_name, "1.0.*",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}}},
+ {application_name, "",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}},
+ [raw]}]}
+ ]).
+
%% Added because of trans deps,
%% need all deps in same dir and should be the one set by the root rebar.config
+%% In case one is given globally, it has higher priority
%% Sets a default if root config has no deps_dir set
set_shared_deps_dir(Config, []) ->
- GlobalDepsDir = rebar_config:get_global(Config, deps_dir, "deps"),
- DepsDir = rebar_config:get_local(Config, deps_dir, GlobalDepsDir),
+ LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"),
+ DepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir),
rebar_config:set_xconf(Config, deps_dir, DepsDir);
set_shared_deps_dir(Config, _DepsDir) ->
Config.
diff --git a/src/rebar_dia_compiler.erl b/src/rebar_dia_compiler.erl
index 51c075b..f81c734 100644
--- a/src/rebar_dia_compiler.erl
+++ b/src/rebar_dia_compiler.erl
@@ -28,6 +28,9 @@
-export([compile/2, clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -46,6 +49,23 @@ clean(_Config, _AppFile) ->
ok = rebar_file_utils:delete_each(GeneratedFiles),
ok.
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+info(help, compile) ->
+ info_help("Build Diameter (*.dia) sources");
+info(help, clean) ->
+ info_help("Delete generated Diameter files").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " {dia_opts, []} (see diameter_codegen:from_dict/4 documentation)~n",
+ [Description]).
+
-spec compile_dia(file:filename(), file:filename(),
rebar_config:config()) -> ok.
compile_dia(Source, Target, Config) ->
diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl
index cf0239c..c828d27 100644
--- a/src/rebar_edoc.erl
+++ b/src/rebar_edoc.erl
@@ -39,6 +39,9 @@
-export([doc/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -71,6 +74,14 @@ doc(Config, File) ->
%% Internal functions
%% ===================================================================
+info(help, doc) ->
+ ?CONSOLE(
+ "Generate Erlang program documentation.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " {edoc_opts, []} (see edoc:application/3 documentation)~n",
+ []).
+
setup_code_path() ->
%% Setup code path prior to calling edoc so that edown, asciiedoc,
%% and the like can work properly when generating their own
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 91f8354..caef0d2 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -29,8 +29,9 @@
-export([compile/2,
clean/2]).
-%% for internal use by only eunit and qc
--export([test_compile/3]).
+%% for internal use only
+-export([test_compile/3,
+ info/2]).
-include("rebar.hrl").
@@ -116,8 +117,6 @@ clean(_Config, _AppFile) ->
test_compile(Config, Cmd, OutDir) ->
%% Obtain all the test modules for inclusion in the compile stage.
- %% Notice: this could also be achieved with the following
- %% rebar.config option: {test_compile_opts, [{src_dirs, ["src", "test"]}]}
TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
%% Copy source files to eunit dir for cover in case they are not directly
@@ -165,6 +164,42 @@ test_compile(Config, Cmd, OutDir) ->
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Build *.erl, *.yrl, *.xrl, and *.mib sources");
+info(help, clean) ->
+ info_help("Delete *.erl, *.yrl, *.xrl, and *.mib build results").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ Description,
+ {erl_opts, [no_debug_info,
+ {i, "myinclude"},
+ {src_dirs, ["src", "src2", "src3"]},
+ {platform_define,
+ "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'},
+ {platform_define, "(linux|freebsd)", 'BACKLOG', 128},
+ {platform_define, "R13", 'old_inets'}]},
+ {erl_first_files, ["mymib1", "mymib2"]},
+ {mib_opts, []},
+ {mib_first_files, []},
+ {xrl_opts, []},
+ {xrl_first_files, []},
+ {yrl_opts, []},
+ {yrl_first_files, []}
+ ]).
+
test_compile_config(Config, Cmd) ->
{Config1, TriqOpts} = triq_opts(Config),
{Config2, PropErOpts} = proper_opts(Config1),
diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl
index b5fe899..4449be6 100644
--- a/src/rebar_erlydtl_compiler.erl
+++ b/src/rebar_erlydtl_compiler.erl
@@ -96,6 +96,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -123,23 +126,39 @@ compile(Config, _AppFile) ->
true = code:set_path(OrigPath),
Result.
-
%% ===================================================================
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ ?CONSOLE(
+ "Build ErlyDtl (*.dtl) sources.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {erlydtl_opts, [{doc_root, "templates"},
+ {out_dir, "ebin"},
+ {source_ext, ".dtl"},
+ {module_ext, "_dtl"},
+ {recursive, true}]}
+ ]).
+
erlydtl_opts(Config) ->
Opts = rebar_config:get(Config, erlydtl_opts, []),
Tuples = [{K,V} || {K,V} <- Opts],
case [L || L <- Opts, is_list(L), not io_lib:printable_list(L)] of
[] ->
- lists:keysort(1, Tuples);
+ [lists:keysort(1, Tuples)];
Lists ->
- lists:map(fun(L) ->
- lists:keysort(1, lists:foldl(fun({K,T}, Acc) ->
- lists:keystore(K, 1, Acc, {K, T})
- end, Tuples, L))
- end, Lists)
+ lists:map(
+ fun(L) ->
+ lists:keysort(1,
+ lists:foldl(
+ fun({K,T}, Acc) ->
+ lists:keystore(K, 1, Acc, {K, T})
+ end, Tuples, L))
+ end, Lists)
end.
option(Opt, DtlOpts) ->
diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl
index 706cf7c..0cc43ef 100644
--- a/src/rebar_escripter.erl
+++ b/src/rebar_escripter.erl
@@ -29,6 +29,9 @@
-export([escriptize/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-include_lib("kernel/include/file.hrl").
@@ -108,6 +111,30 @@ clean(Config0, AppFile) ->
%% Internal functions
%% ===================================================================
+info(help, escriptize) ->
+ info_help("Generate escript archive");
+info(help, clean) ->
+ info_help("Delete generated escript archive").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ Description,
+ {escript_name, "application"},
+ {escript_incl_apps, []},
+ {escript_shebang, "#!/usr/bin/env escript\n"},
+ {escript_comment, "%%\n"},
+ {escript_emu_args, "%%! -pa application/application/ebin\n"}
+ ]).
+
get_app_beams([], Acc) ->
Acc;
get_app_beams([App | Rest], Acc) ->
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index a6a68ce..95ba3e8 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -61,7 +61,7 @@
%% Additionally, for projects that have separate folders for the core
%% implementation, and for the unit tests, then the following
%% <code>rebar.config</code> option can be provided:
-%% <code>{test_compile_opts, [{src_dirs, ["dir"]}]}.</code>.
+%% <code>{eunit_compile_opts, [{src_dirs, ["src", "dir"]}]}.</code>.
%% @copyright 2009, 2010 Dave Smith
%% -------------------------------------------------------------------
-module(rebar_eunit).
@@ -69,6 +69,9 @@
-export([eunit/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(EUNIT_DIR, ".eunit").
@@ -100,6 +103,40 @@ clean(_Config, _File) ->
%% Internal functions
%% ===================================================================
+info(help, eunit) ->
+ info_help("Run eunit tests");
+info(help, clean) ->
+ Description = ?FMT("Delete eunit test dir (~s)", [?EUNIT_DIR]),
+ info_help(Description).
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ "Valid command line options:~n"
+ " suites=\"foo,bar\" (Run tests in foo.erl, test/foo_tests.erl and~n"
+ " tests in bar.erl, test/bar_tests.erl)~n"
+ " tests=\"baz\" (For every existing suite, run the first test whose~n"
+ " name starts with bar and, if no such test exists,~n"
+ " run the test whose name starts with bar in the~n"
+ " suite's _tests module)~n",
+ [
+ Description,
+ {eunit_opts, []},
+ {eunit_compile_opts, []},
+ {eunit_first_files, []},
+ {cover_enabled, false},
+ {cover_print_enabled, false},
+ {cover_export_enabled, false}
+ ]).
+
run_eunit(Config, CodePath, SrcErls) ->
%% Build a list of all the .beams in ?EUNIT_DIR -- use this for
%% cover and eunit testing. Normally you can just tell cover
@@ -186,7 +223,7 @@ filter_suites(Config, Modules) ->
filter_suites1(Modules, []) ->
Modules;
filter_suites1(Modules, Suites) ->
- [M || M <- Modules, lists:member(M, Suites)].
+ [M || M <- Suites, lists:member(M, Modules)].
%%
%% == get matching tests ==
@@ -632,7 +669,8 @@ reset_after_eunit({OldProcesses, WasAlive, OldAppEnvs, _OldACs}) ->
end,
ok = application:unset_env(App, K)
end || App <- Apps, App /= rebar,
- {K, _V} <- application:get_all_env(App)],
+ {K, _V} <- application:get_all_env(App),
+ K =/= included_applications],
reconstruct_app_env_vars(Apps),
@@ -765,7 +803,6 @@ pause_until_net_kernel_stopped(0) ->
exit(net_kernel_stop_failed);
pause_until_net_kernel_stopped(N) ->
try
- _ = net_kernel:i(),
timer:sleep(100),
pause_until_net_kernel_stopped(N - 1)
catch
diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl
index d288ca5..2a047d8 100644
--- a/src/rebar_lfe_compiler.erl
+++ b/src/rebar_lfe_compiler.erl
@@ -30,6 +30,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -45,6 +48,14 @@ compile(Config, _AppFile) ->
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ ?CONSOLE(
+ "Build Lisp Flavoured Erlang (*.lfe) sources.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " erl_opts is reused.'~n",
+ []).
+
compile_lfe(Source, _Target, Config) ->
case code:which(lfe_comp) of
non_existing ->
diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl
index 33f32e3..b9f23f2 100644
--- a/src/rebar_neotoma_compiler.erl
+++ b/src/rebar_neotoma_compiler.erl
@@ -42,6 +42,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ============================================================================
@@ -53,13 +56,26 @@ compile(Config, _AppFile) ->
rebar_base_compiler:run(Config, [],
option(doc_root, NeoOpts), ".peg",
option(out_dir, NeoOpts),
- option(module_ext, NeoOpts) ++ ".beam",
- fun compile_neo/3, [{check_last_mod,false}]).
+ option(module_ext, NeoOpts) ++ ".erl",
+ fun compile_neo/3, [{check_last_mod, true}]).
%% ============================================================================
%% Internal functions
%% ============================================================================
+info(help, compile) ->
+ ?CONSOLE(
+ "Build Neotoma (*.peg) sources.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {neotom_opts, [{doc_root, "src"},
+ {out_dir, "src"},
+ {source_ext, ".peg"},
+ {module_ext, ""}]}
+ ]).
+
neotoma_opts(Config) ->
rebar_config:get(Config, neotoma_opts, []).
diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl
index a62f584..b3566c8 100644
--- a/src/rebar_otp_app.erl
+++ b/src/rebar_otp_app.erl
@@ -29,6 +29,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -82,11 +85,26 @@ clean(_Config, File) ->
ok
end.
-
%% ===================================================================
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Validate .app file");
+info(help, clean) ->
+ info_help("Delete .app file if generated from .app.src").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ Description,
+ {validate_app_modules, true}
+ ]).
+
preprocess(Config, AppSrcFile) ->
case rebar_app_utils:load_app_file(Config, AppSrcFile) of
{ok, Config1, AppName, AppData} ->
@@ -101,8 +119,12 @@ preprocess(Config, AppSrcFile) ->
{Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppSrcFile),
A2 = lists:keystore(vsn, 1, A1, {vsn, Vsn}),
+ %% systools:make_relup/4 fails with {missing_param, registered}
+ %% without a 'registered' value.
+ A3 = ensure_registered(A2),
+
%% Build the final spec as a string
- Spec = io_lib:format("~p.\n", [{application, AppName, A2}]),
+ Spec = io_lib:format("~p.\n", [{application, AppName, A3}]),
%% Setup file .app filename and write new contents
AppFile = rebar_app_utils:app_src_to_app(AppSrcFile),
@@ -187,3 +209,12 @@ validate_modules(AppName, Mods) ->
ebin_modules() ->
lists:sort([rebar_utils:beam_to_mod("ebin", N) ||
N <- rebar_utils:beams("ebin")]).
+
+ensure_registered(AppData) ->
+ case lists:keyfind(registered, 1, AppData) of
+ false ->
+ [{registered, []} | AppData];
+ {registered, _} ->
+ %% We could further check whether the value is a list of atoms.
+ AppData
+ end.
diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl
index 06a79bc..0abb044 100644
--- a/src/rebar_port_compiler.erl
+++ b/src/rebar_port_compiler.erl
@@ -27,8 +27,11 @@
-module(rebar_port_compiler).
-export([compile/2,
- clean/2,
- setup_env/1]).
+ clean/2]).
+
+%% for internal use only
+-export([setup_env/1,
+ info/2]).
-include("rebar.hrl").
@@ -97,7 +100,8 @@ compile(Config, AppFile) ->
[] ->
ok;
Specs ->
- SharedEnv = rebar_config:get_env(Config, ?MODULE),
+ SharedEnv = rebar_config:get_env(Config, rebar_deps) ++
+ rebar_config:get_env(Config, ?MODULE),
%% Compile each of the sources
NewBins = compile_sources(Config, Specs, SharedEnv),
@@ -149,6 +153,27 @@ setup_env(Config) ->
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Build port sources");
+info(help, clean) ->
+ info_help("Delete port build results").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ Description,
+ {port_env, [{"CFLAGS", "$CFLAGS -Ifoo"},
+ {"freebsd", "LDFLAGS", "$LDFLAGS -lfoo"}]},
+ {port_specs, [{"priv/so_name.so", ["c_src/*.c"]},
+ {"linux", "priv/hello_linux", ["c_src/hello_linux.c"]},
+ {"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}]}
+ ]).
+
setup_env(Config, ExtraEnv) ->
%% Extract environment values from the config (if specified) and
%% merge with the default for this operating system. This enables
diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl
index 7ef58d6..579ecfb 100644
--- a/src/rebar_protobuffs_compiler.erl
+++ b/src/rebar_protobuffs_compiler.erl
@@ -29,6 +29,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -57,7 +60,6 @@ compile(Config, _AppFile) ->
end
end.
-
clean(_Config, _AppFile) ->
%% Get a list of generated .beam and .hrl files and then delete them
Protos = rebar_utils:find_files("src", ".*\\.proto$"),
@@ -71,11 +73,24 @@ clean(_Config, _AppFile) ->
delete_each(Targets)
end.
-
%% ===================================================================
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Build Protobuffs (*.proto) sources");
+info(help, clean) ->
+ info_help("Delete Protobuffs (*.proto) build results").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " erl_opts is passed as compile_flags to "
+ "protobuffs_compile:scan_file/2~n",
+ [Description]).
+
protobuffs_is_present() ->
code:which(protobuffs_compile) =/= non_existing.
@@ -115,7 +130,7 @@ compile_each(Config, [{Proto, Beam, Hrl} | Rest]) ->
ok = rebar_file_utils:mv(Hrl, "include"),
ok;
Other ->
- ?ERROR("Protobuff compile of ~s failed: ~p\n",
+ ?ERROR("Protobuffs compile of ~s failed: ~p\n",
[Proto, Other]),
?FAIL
end;
diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl
index 09e48e4..53a6f52 100644
--- a/src/rebar_qc.erl
+++ b/src/rebar_qc.erl
@@ -28,6 +28,9 @@
-export([qc/2, triq/2, eqc/2, clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(QC_DIR, ".qc").
@@ -57,6 +60,19 @@ clean(_Config, _File) ->
%% Internal functions
%% ===================================================================
+info(help, qc) ->
+ ?CONSOLE(
+ "Test QuickCheck properties.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " {qc_opts, [{qc_mod, module()}, Options]}~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {qc_compile_opts, []},
+ {qc_first_files, []}
+ ]).
+
-define(TRIQ_MOD, triq).
-define(EQC_MOD, eqc).
@@ -159,6 +175,7 @@ qc_module(QC=triq, _QCOpts, M) ->
Failed ->
[Failed]
end;
+qc_module(QC=eqc, [], M) -> QC:module(M);
qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
find_prop_mods() ->
diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl
index 3c9b728..9f9488e 100644
--- a/src/rebar_reltool.erl
+++ b/src/rebar_reltool.erl
@@ -30,6 +30,9 @@
overlay/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-include_lib("kernel/include/file.hrl").
@@ -80,6 +83,32 @@ clean(Config, ReltoolFile) ->
%% Internal functions
%% ===================================================================
+info(help, generate) ->
+ info_help("Build release with reltool");
+info(help, clean) ->
+ info_help("Delete release");
+info(help, overlay) ->
+ info_help("Run reltool overlays only").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~n"
+ "Valid reltool.config options:~n"
+ " {sys, []}~n"
+ " {target_dir, \"target\"}~n"
+ " {overlay_vars, \"overlay\"}~n"
+ " {overlay, []}~n"
+ "Valid command line options:~n"
+ " target_dir=target~n"
+ " overlay_vars=VarsFile~n"
+ " dump_spec=1 (write reltool target spec to reltool.spec)~n",
+ [
+ Description
+ ]).
+
check_vsn() ->
%% TODO: use application:load and application:get_key once we require
%% R14A or newer. There's no reltool.app before R14A.
@@ -137,22 +166,32 @@ process_overlay(Config, ReltoolConfig) ->
%% providing an additional file on the command-line.
%%
overlay_vars(Config, Vars0, ReltoolConfig) ->
- BaseVars = load_vars_file(proplists:get_value(overlay_vars, ReltoolConfig)),
- OverrideVars = load_vars_file(rebar_config:get_global(Config,
- overlay_vars,
- undefined)),
- M = fun(_Key, _Base, Override) -> Override end,
+ BaseVars = load_vars_file([proplists:get_value(overlay_vars, ReltoolConfig)]),
+ OverlayVars = rebar_config:get_global(Config, overlay_vars, []),
+ OverrideVars = load_vars_file(string:tokens(OverlayVars, ",")),
+ M = fun merge_overlay_var/3,
dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars).
+merge_overlay_var(_Key, _Base, Override) -> Override.
+
%%
%% If a filename is provided, construct a dict of terms
%%
-load_vars_file(undefined) ->
+load_vars_file([undefined]) ->
dict:new();
-load_vars_file(File) ->
+load_vars_file([]) ->
+ dict:new();
+load_vars_file(Files) ->
+ load_vars_file(Files, dict:new()).
+
+load_vars_file([], Dict) ->
+ Dict;
+load_vars_file([File | Files], BaseVars) ->
case rebar_config:consult_file(File) of
{ok, Terms} ->
- dict:from_list(Terms);
+ OverrideVars = dict:from_list(Terms),
+ M = fun merge_overlay_var/3,
+ load_vars_file(Files, dict:merge(M, BaseVars, OverrideVars));
{error, Reason} ->
?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason])
end.
diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl
index 9a0a005..385f55c 100644
--- a/src/rebar_require_vsn.erl
+++ b/src/rebar_require_vsn.erl
@@ -33,6 +33,9 @@
-export([compile/2,
eunit/2]).
+%% for internal use only
+-export([info/2]).
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -47,6 +50,25 @@ eunit(Config, _) ->
%% Internal functions
%% ====================================================================
+info(help, compile) ->
+ info_help();
+info(help, eunit) ->
+ info_help().
+
+info_help() ->
+ ?CONSOLE(
+ "Check required ERTS or OTP release version.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {require_erts_vsn, ".*"},
+ {require_otp_vsn, ".*"},
+ {require_min_otp_vsn, ".*"}
+ ]).
+
check_versions(Config) ->
ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"),
ReOpts = [{capture, none}],
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index 0e1eef1..e997975 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -35,6 +35,9 @@
-export([resolve_variables/2,
render/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(TEMPLATE_RE, ".*\\.template\$").
@@ -81,6 +84,9 @@ resolve_variables([], Dict) ->
resolve_variables([{Key, Value0} | Rest], Dict) when is_list(Value0) ->
Value = render(list_to_binary(Value0), Dict),
resolve_variables(Rest, dict:store(Key, Value, Dict));
+resolve_variables([{Key, {list, Dicts}} | Rest], Dict) when is_list(Dicts) ->
+ %% just un-tag it so mustache can use it
+ resolve_variables(Rest, dict:store(Key, Dicts, Dict));
resolve_variables([_Pair | Rest], Dict) ->
resolve_variables(Rest, Dict).
@@ -98,6 +104,27 @@ render(Bin, Context) ->
%% Internal functions
%% ===================================================================
+info(help, create) ->
+ ?CONSOLE(
+ "Create skel based on template and vars.~n"
+ "~n"
+ "Valid command line options:~n"
+ " template= [var=foo,...]~n", []);
+info(help, 'create-app') ->
+ ?CONSOLE(
+ "Create simple app skel.~n"
+ "~n"
+ "Valid command line options:~n"
+ " [appid=myapp]~n", []);
+info(help, 'create-node') ->
+ ?CONSOLE(
+ "Create simple node skel.~n"
+ "~n"
+ "Valid command line options:~n"
+ " [nodeid=mynode]~n", []);
+info(help, 'list-templates') ->
+ ?CONSOLE("List available templates.~n", []).
+
create1(Config, TemplateId) ->
{AvailTemplates, Files} = find_templates(Config),
?DEBUG("Available templates: ~p\n", [AvailTemplates]),
@@ -134,14 +161,14 @@ create1(Config, TemplateId) ->
undefined ->
Context0;
File ->
- case file:consult(File) of
- {ok, Terms} ->
- %% TODO: Cleanup/merge with similar code in rebar_reltool
- M = fun(_Key, _Base, Override) -> Override end,
- dict:merge(M, Context0, dict:from_list(Terms));
+ case consult(load_file([], file, File)) of
{error, Reason} ->
?ABORT("Unable to load template_vars from ~s: ~p\n",
- [File, Reason])
+ [File, Reason]);
+ Terms ->
+ %% TODO: Cleanup/merge with similar code in rebar_reltool
+ M = fun(_Key, _Base, Override) -> Override end,
+ dict:merge(M, Context0, dict:from_list(Terms))
end
end,
@@ -275,7 +302,7 @@ consult(Cont, Str, Acc) ->
case Result of
{ok, Tokens, _} ->
{ok, Term} = erl_parse:parse_term(Tokens),
- consult([], Remaining, [Term | Acc]);
+ consult([], Remaining, [maybe_dict(Term) | Acc]);
{eof, _Other} ->
lists:reverse(Acc);
{error, Info, _} ->
@@ -286,6 +313,13 @@ consult(Cont, Str, Acc) ->
end.
+maybe_dict({Key, {list, Dicts}}) ->
+ %% this is a 'list' element; a list of lists representing dicts
+ {Key, {list, [dict:from_list(D) || D <- Dicts]}};
+maybe_dict(Term) ->
+ Term.
+
+
write_file(Output, Data, Force) ->
%% determine if the target file already exists
FileExists = filelib:is_regular(Output),
diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl
index 14ea758..d18603c 100644
--- a/src/rebar_upgrade.erl
+++ b/src/rebar_upgrade.erl
@@ -32,6 +32,9 @@
-export(['generate-upgrade'/2]).
+%% for internal use only
+-export([info/2]).
+
-define(TMP, "_tmp").
%% ====================================================================
@@ -80,6 +83,13 @@
%% Internal functions
%% ==================================================================
+info(help, 'generate-upgrade') ->
+ ?CONSOLE("Build an upgrade package.~n"
+ "~n"
+ "Valid command line options:~n"
+ " previous_release=path~n",
+ []).
+
run_checks(Config, OldVerPath, ReltoolConfig) ->
true = rebar_utils:prop_check(filelib:is_dir(OldVerPath),
"Release directory doesn't exist (~p)~n",
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index bb58460..64ac4a9 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -191,11 +191,11 @@ expand_env_variable(InStr, VarName, RawVarValue) ->
%% No variables to expand
InStr;
_ ->
- VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", [global]),
+ ReOpts = [global, unicode, {return, list}],
+ VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts),
%% Use a regex to match/replace:
%% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO}
RegEx = io_lib:format("\\\$(~s(\\s|$)|{~s})", [VarName, VarName]),
- ReOpts = [global, {return, list}],
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
end.
diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl
index a55d71d..eaf6d03 100644
--- a/src/rebar_xref.erl
+++ b/src/rebar_xref.erl
@@ -37,6 +37,9 @@
-export([xref/2]).
+%% for internal use only
+-export([info/2]).
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -57,27 +60,20 @@ xref(Config, _) ->
true = code:add_path(rebar_utils:ebin_dir()),
%% Get list of xref checks we want to run
- XrefChecks = rebar_config:get(Config, xref_checks,
- [exports_not_used,
- undefined_function_calls]),
-
- %% Look for exports that are unused by anything
- ExportsNoWarn =
- case lists:member(exports_not_used, XrefChecks) of
- true ->
- check_exports_not_used();
- false ->
- true
- end,
+ ConfXrefChecks = rebar_config:get(Config, xref_checks,
+ [exports_not_used,
+ undefined_function_calls]),
- %% Look for calls to undefined functions
- UndefNoWarn =
- case lists:member(undefined_function_calls, XrefChecks) of
- true ->
- check_undefined_function_calls();
- false ->
- true
- end,
+ SupportedXrefs = [undefined_function_calls, undefined_functions,
+ locals_not_used, exports_not_used,
+ deprecated_function_calls, deprecated_functions],
+
+ XrefChecks = sets:to_list(sets:intersection(
+ sets:from_list(SupportedXrefs),
+ sets:from_list(ConfXrefChecks))),
+
+ %% Run xref checks
+ XrefNoWarn = xref_checks(XrefChecks),
%% Run custom queries
QueryChecks = rebar_config:get(Config, xref_queries, []),
@@ -89,7 +85,7 @@ xref(Config, _) ->
%% Stop xref
stopped = xref:stop(xref),
- case lists:member(false, [ExportsNoWarn, UndefNoWarn, QueryNoWarn]) of
+ case lists:member(false, [XrefNoWarn, QueryNoWarn]) of
true ->
?FAIL;
false ->
@@ -100,26 +96,36 @@ xref(Config, _) ->
%% Internal functions
%% ===================================================================
-check_exports_not_used() ->
- {ok, UnusedExports0} = xref:analyze(xref, exports_not_used),
- UnusedExports = filter_away_ignored(UnusedExports0),
+info(help, xref) ->
+ ?CONSOLE(
+ "Run cross reference analysis.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {xref_warnings, false},
+ {xref_checks, [undefined_function_calls, undefined_functions,
+ locals_not_used, exports_not_used,
+ deprecated_function_calls, deprecated_functions]},
+ {xref_queries,
+ [{"(xc - uc) || (xu - x - b"
+ " - (\"mod\":\".*foo\"/\"4\"))",[]}]}
+ ]).
- %% Report all the unused functions
- display_mfas(UnusedExports, "is unused export (Xref)"),
- UnusedExports =:= [].
+xref_checks(XrefChecks) ->
+ XrefWarnCount = lists:foldl(fun run_xref_check/2, 0, XrefChecks),
+ XrefWarnCount =:= 0.
-check_undefined_function_calls() ->
- {ok, UndefinedCalls0} = xref:analyze(xref, undefined_function_calls),
- UndefinedCalls =
- [{find_mfa_source(Caller), format_fa(Caller), format_mfa(Target)}
- || {Caller, Target} <- UndefinedCalls0],
-
- lists:foreach(
- fun({{Source, Line}, FunStr, Target}) ->
- ?CONSOLE("~s:~w: Warning ~s calls undefined function ~s\n",
- [Source, Line, FunStr, Target])
- end, UndefinedCalls),
- UndefinedCalls =:= [].
+run_xref_check(XrefCheck, Acc) ->
+ {ok, Results} = xref:analyze(xref, XrefCheck),
+ FilteredResults =filter_xref_results(XrefCheck, Results),
+ lists:foreach(fun(Res) ->
+ display_xref_result(XrefCheck, Res)
+ end,
+ FilteredResults),
+ Acc + length(FilteredResults).
check_query({Query, Value}) ->
{ok, Answer} = xref:q(xref, Query),
@@ -147,41 +153,98 @@ code_path(Config) ->
%%
%% Ignore behaviour functions, and explicitly marked functions
%%
-filter_away_ignored(UnusedExports) ->
- %% Functions can be ignored by using
- %% -ignore_xref([{F, A}, ...]).
-
- %% Setup a filter function that builds a list of behaviour callbacks and/or
- %% any functions marked to ignore. We then use this list to mask any
- %% functions marked as unused exports by xref
- F = fun(Mod) ->
- Attrs = Mod:module_info(attributes),
- Ignore = keyall(ignore_xref, Attrs),
- Callbacks = [B:behaviour_info(callbacks)
- || B <- keyall(behaviour, Attrs)],
- [{Mod, F, A} || {F, A} <- Ignore ++ lists:flatten(Callbacks)]
+%% Functions can be ignored by using
+%% -ignore_xref([{F, A}, {M, F, A}...]).
+
+get_xref_ignorelist(Mod, XrefCheck) ->
+ %% Get ignore_xref attribute and combine them in one list
+ Attributes =
+ try
+ Mod:module_info(attributes)
+ catch
+ _Class:_Error -> []
end,
- AttrIgnore =
- lists:flatten(
- lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))),
- [X || X <- UnusedExports, not lists:member(X, AttrIgnore)].
+
+ IgnoreXref = keyall(ignore_xref, Attributes),
+
+ BehaviourCallbacks = get_behaviour_callbacks(XrefCheck, Attributes),
+
+ %% And create a flat {M,F,A} list
+ lists:foldl(
+ fun({F, A}, Acc) -> [{Mod,F,A} | Acc];
+ ({M, F, A}, Acc) -> [{M,F,A} | Acc]
+ end, [], lists:flatten([IgnoreXref, BehaviourCallbacks])).
keyall(Key, List) ->
lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
-display_mfas([], _Message) ->
- ok;
-display_mfas([{_Mod, Fun, Args} = MFA | Rest], Message) ->
- {Source, Line} = find_mfa_source(MFA),
- ?CONSOLE("~s:~w: Warning: function ~s/~w ~s\n",
- [Source, Line, Fun, Args, Message]),
- display_mfas(Rest, Message).
+get_behaviour_callbacks(exports_not_used, Attributes) ->
+ [B:behaviour_info(callbacks) || B <- keyall(behaviour, Attributes)];
+get_behaviour_callbacks(_XrefCheck, _Attributes) ->
+ [].
+
+parse_xref_result({_, MFAt}) -> MFAt;
+parse_xref_result(MFAt) -> MFAt.
+
+filter_xref_results(XrefCheck, XrefResults) ->
+ SearchModules = lists:usort(
+ lists:map(
+ fun({Mt,_Ft,_At}) -> Mt;
+ ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms;
+ (_) -> undefined
+ end, XrefResults)),
+
+ Ignores = lists:flatmap(fun(Module) ->
+ get_xref_ignorelist(Module, XrefCheck)
+ end, SearchModules),
+
+ [Result || Result <- XrefResults,
+ not lists:member(parse_xref_result(Result), Ignores)].
+
+display_xref_result(Type, XrefResult) ->
+ { Source, SMFA, TMFA } = case XrefResult of
+ {MFASource, MFATarget} ->
+ {format_mfa_source(MFASource),
+ format_mfa(MFASource),
+ format_mfa(MFATarget)};
+ MFATarget ->
+ {format_mfa_source(MFATarget),
+ format_mfa(MFATarget),
+ undefined}
+ end,
+ case Type of
+ undefined_function_calls ->
+ ?CONSOLE("~sWarning: ~s calls undefined function ~s (Xref)\n",
+ [Source, SMFA, TMFA]);
+ undefined_functions ->
+ ?CONSOLE("~sWarning: ~s is undefined function (Xref)\n",
+ [Source, SMFA]);
+ locals_not_used ->
+ ?CONSOLE("~sWarning: ~s is unused local function (Xref)\n",
+ [Source, SMFA]);
+ exports_not_used ->
+ ?CONSOLE("~sWarning: ~s is unused export (Xref)\n",
+ [Source, SMFA]);
+ deprecated_function_calls ->
+ ?CONSOLE("~sWarning: ~s calls deprecated function ~s (Xref)\n",
+ [Source, SMFA, TMFA]);
+ deprecated_functions ->
+ ?CONSOLE("~sWarning: ~s is deprecated function (Xref)\n",
+ [Source, SMFA]);
+ Other ->
+ ?CONSOLE("~sWarning: ~s - ~s xref check: ~s (Xref)\n",
+ [Source, SMFA, TMFA, Other])
+ end.
format_mfa({M, F, A}) ->
?FMT("~s:~s/~w", [M, F, A]).
-format_fa({_M, F, A}) ->
- ?FMT("~s/~w", [F, A]).
+format_mfa_source(MFA) ->
+ case find_mfa_source(MFA) of
+ {module_not_found, function_not_found} -> "";
+ {Source, function_not_found} -> ?FMT("~s: ", [Source]);
+ {Source, Line} -> ?FMT("~s:~w: ", [Source, Line])
+ end.
%%
%% Extract an element from a tuple, or undefined if N > tuple size
@@ -194,14 +257,18 @@ safe_element(N, Tuple) ->
Value
end.
-
%%
%% Given a MFA, find the file and LOC where it's defined. Note that
%% xref doesn't work if there is no abstract_code, so we can avoid
%% being too paranoid here.
%%
find_mfa_source({M, F, A}) ->
- {M, Bin, _} = code:get_object_code(M),
+ case code:get_object_code(M) of
+ error -> {module_not_found, function_not_found};
+ {M, Bin, _} -> find_function_source(M,F,A,Bin)
+ end.
+
+find_function_source(M, F, A, Bin) ->
AbstractCode = beam_lib:chunks(Bin, [abstract_code]),
{ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode,
%% Extract the original source filename from the abstract code