diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/getopt.erl | 533 | ||||
-rw-r--r-- | src/rebar.erl | 56 | ||||
-rw-r--r-- | src/rebar_abnfc_compiler.erl | 17 | ||||
-rw-r--r-- | src/rebar_appups.erl | 10 | ||||
-rw-r--r-- | src/rebar_asn1_compiler.erl | 20 | ||||
-rw-r--r-- | src/rebar_base_compiler.erl | 2 | ||||
-rw-r--r-- | src/rebar_cleaner.erl | 17 | ||||
-rw-r--r-- | src/rebar_core.erl | 122 | ||||
-rw-r--r-- | src/rebar_ct.erl | 55 | ||||
-rw-r--r-- | src/rebar_deps.erl | 41 | ||||
-rw-r--r-- | src/rebar_dia_compiler.erl | 20 | ||||
-rw-r--r-- | src/rebar_edoc.erl | 11 | ||||
-rw-r--r-- | src/rebar_erlc_compiler.erl | 43 | ||||
-rw-r--r-- | src/rebar_erlydtl_compiler.erl | 33 | ||||
-rw-r--r-- | src/rebar_escripter.erl | 27 | ||||
-rw-r--r-- | src/rebar_eunit.erl | 45 | ||||
-rw-r--r-- | src/rebar_lfe_compiler.erl | 11 | ||||
-rw-r--r-- | src/rebar_neotoma_compiler.erl | 20 | ||||
-rw-r--r-- | src/rebar_otp_app.erl | 35 | ||||
-rw-r--r-- | src/rebar_port_compiler.erl | 31 | ||||
-rw-r--r-- | src/rebar_protobuffs_compiler.erl | 21 | ||||
-rw-r--r-- | src/rebar_qc.erl | 17 | ||||
-rw-r--r-- | src/rebar_reltool.erl | 55 | ||||
-rw-r--r-- | src/rebar_require_vsn.erl | 22 | ||||
-rw-r--r-- | src/rebar_templater.erl | 48 | ||||
-rw-r--r-- | src/rebar_upgrade.erl | 10 | ||||
-rw-r--r-- | src/rebar_utils.erl | 4 | ||||
-rw-r--r-- | src/rebar_xref.erl | 201 |
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 |