summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/getopt.erl621
-rw-r--r--src/rebar.erl157
-rw-r--r--src/rebar_app_utils.erl9
-rw-r--r--src/rebar_base_compiler.erl44
-rw-r--r--src/rebar_config.erl5
-rw-r--r--src/rebar_core.erl69
-rw-r--r--src/rebar_ct.erl61
-rw-r--r--src/rebar_deps.erl237
-rw-r--r--src/rebar_dia_compiler.erl4
-rw-r--r--src/rebar_erlc_compiler.erl483
-rw-r--r--src/rebar_erlydtl_compiler.erl63
-rw-r--r--src/rebar_eunit.erl88
-rw-r--r--src/rebar_getopt.erl914
-rw-r--r--src/rebar_lfe_compiler.erl4
-rw-r--r--src/rebar_log.erl37
-rw-r--r--src/rebar_mustache.erl (renamed from src/mustache.erl)10
-rw-r--r--src/rebar_neotoma_compiler.erl12
-rw-r--r--src/rebar_otp_app.erl15
-rw-r--r--src/rebar_qc.erl7
-rw-r--r--src/rebar_reltool.erl26
-rw-r--r--src/rebar_templater.erl34
-rw-r--r--src/rebar_upgrade.erl33
-rw-r--r--src/rebar_utils.erl25
-rw-r--r--src/rebar_xref.erl193
24 files changed, 2057 insertions, 1094 deletions
diff --git a/src/getopt.erl b/src/getopt.erl
deleted file mode 100644
index 175b7a5..0000000
--- a/src/getopt.erl
+++ /dev/null
@@ -1,621 +0,0 @@
-%%%-------------------------------------------------------------------
-%%% @author Juan Jose Comellas <juanjo@comellas.org>
-%%% @copyright (C) 2009 Juan Jose Comellas
-%%% @doc Parses command line options with a format similar to that of GNU getopt.
-%%% @end
-%%%
-%%% This source file is subject to the New BSD License. You should have received
-%%% a copy of the New BSD license with this software. If not, it can be
-%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php
-%%%-------------------------------------------------------------------
--module(getopt).
--author('juanjo@comellas.org').
-
--export([parse/2, usage/2, usage/3, usage/4]).
-
--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).
-
-%% Position of each field in the option specification tuple.
--define(OPT_NAME, 1).
--define(OPT_SHORT, 2).
--define(OPT_LONG, 3).
--define(OPT_ARG, 4).
--define(OPT_HELP, 5).
-
--define(IS_OPT_SPEC(Opt), (tuple_size(Opt) =:= ?OPT_HELP)).
-
-
-%% Atom indicating the data type that an argument can be converted to.
--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().
-%% Argument specification.
--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().
-%% Command line option specification.
--type option_spec() :: {
- Name :: atom(),
- Short :: char() | undefined,
- Long :: string() | undefined,
- ArgSpec :: arg_spec(),
- Help :: string() | undefined
- }.
-%% Output streams
--type output_stream() :: 'standard_io' | 'standard_error'.
-
-
-%% @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) ->
- try
- Args = if
- is_integer(hd(CmdLine)) ->
- string:tokens(CmdLine, " \t\n");
- true ->
- CmdLine
- end,
- parse(OptSpecList, [], [], 0, Args)
- catch
- throw: {error, {_Reason, _Data}} = Error ->
- Error
- end.
-
-
--spec parse([option_spec()], [option()], [string()], integer(), [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.
- {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}};
-%% Process long options.
-parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) ->
- parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg);
-%% Process short options.
-parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) ->
- parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg);
-%% Process non-option arguments.
-parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) ->
- case find_non_option_arg(OptSpecList, ArgPos) of
- {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) ->
- parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos + 1, Tail);
- false ->
- parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail)
- end;
-parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
- %% Once we have completed gathering the options we add the ones that were
- %% not present but had default arguments in the specification.
- {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}.
-
-
-%% @doc Parse a long option, add it to the option accumulator and continue
-%% parsing the rest of the arguments recursively.
-%% A long option can have the following syntax:
-%% --foo Single option 'foo', no argument
-%% --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()]}}.
-parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
- case split_assigned_arg(OptArg) of
- {Long, Arg} ->
- %% Get option that has its argument within the same string
- %% separated by an equal ('=') character (e.g. "--port=1000").
- parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg);
-
- Long ->
- case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of
- {Name, _Short, Long, undefined, _Help} ->
- parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args);
-
- {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec ->
- %% The option argument string is empty, but the option requires
- %% an argument, so we look into the next string in the list.
- %% e.g ["--port", "1000"]
- parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec);
- false ->
- throw({error, {invalid_option, OptStr}})
- end
- end.
-
-
-%% @doc Parse an option where the argument is 'assigned' in the same string using
-%% the '=' character, add it to the option accumulator and continue parsing the
-%% rest of the arguments recursively. This syntax is only valid for long options.
--spec parse_long_option_assigned_arg([option_spec()], [option()], [string()], integer(),
- [string()], string(), string(), string()) ->
- {ok, {[option()], [string()]}}.
-parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) ->
- case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of
- {_Name, _Short, Long, ArgSpec, _Help} = OptSpec ->
- case ArgSpec of
- undefined ->
- throw({error, {invalid_option_arg, OptStr}});
- _ ->
- parse(OptSpecList, add_option_with_assigned_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args)
- end;
- false ->
- throw({error, {invalid_option, OptStr}})
- end.
-
-
-%% @doc Split an option string that may contain an option with its argument
-%% separated by an equal ('=') character (e.g. "port=1000").
--spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string().
-split_assigned_arg(OptStr) ->
- split_assigned_arg(OptStr, OptStr, []).
-
-split_assigned_arg(_OptStr, "=" ++ Tail, Acc) ->
- {lists:reverse(Acc), Tail};
-split_assigned_arg(OptStr, [Char | Tail], Acc) ->
- split_assigned_arg(OptStr, Tail, [Char | Acc]);
-split_assigned_arg(OptStr, [], _Acc) ->
- OptStr.
-
-
-%% @doc Retrieve the argument for an option from the next string in the list of
-%% command-line parameters or set the value of the argument from the argument
-%% specification (for boolean and integer arguments), if possible.
-parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) ->
- ArgSpecType = arg_spec_type(ArgSpec),
- case Args =:= [] orelse is_implicit_arg(ArgSpecType, hd(Args)) of
- true ->
- parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
- false ->
- [Arg | Tail] = Args,
- try
- parse(OptSpecList, [{Name, to_type(ArgSpecType, Arg)} | OptAcc], ArgAcc, ArgPos, Tail)
- catch
- error:_ ->
- throw({error, {invalid_option_arg, {Name, Arg}}})
- end
- end.
-
-
-%% @doc Parse a short option, add it to the option accumulator and continue
-%% parsing the rest of the arguments recursively.
-%% A short option can have the following syntax:
-%% -a Single option 'a', no argument or implicit boolean argument
-%% -a foo Single option 'a', argument "foo"
-%% -afoo Single option 'a', argument "foo"
-%% -abc Multiple options: 'a'; 'b'; 'c'
-%% -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()]}}.
-parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
- parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg).
-
-parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptPos, [Short | Arg]) ->
- case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of
- {Name, Short, _Long, undefined, _Help} ->
- parse_short_option(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, first, Arg);
-
- {_Name, Short, _Long, ArgSpec, _Help} = OptSpec ->
- %% The option has a specification, so it requires an argument.
- case Arg of
- [] ->
- %% The option argument string is empty, but the option requires
- %% an argument, so we look into the next string in the list.
- parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec, OptPos);
-
- _ ->
- case is_valid_arg(ArgSpec, Arg) of
- true ->
- parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args);
- _ ->
- NewOptAcc = case OptPos of
- first -> add_option_with_implicit_arg(OptSpec, OptAcc);
- _ -> add_option_with_implicit_incrementable_arg(OptSpec, OptAcc)
- end,
- parse_short_option(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Args, OptStr, next, Arg)
- end
- end;
-
- false ->
- throw({error, {invalid_option, OptStr}})
- end;
-parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, _OptPos, []) ->
- parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args).
-
-
-%% @doc Retrieve the argument for an option from the next string in the list of
-%% command-line parameters or set the value of the argument from the argument
-%% specification (for boolean and integer arguments), if possible.
-parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptPos) ->
- case Args =:= [] orelse is_implicit_arg(ArgSpec, hd(Args)) of
- true when OptPos =:= first ->
- parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
- true ->
- parse(OptSpecList, add_option_with_implicit_incrementable_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
- false ->
- [Arg | Tail] = Args,
- try
- parse(OptSpecList, [{Name, to_type(ArgSpec, Arg)} | OptAcc], ArgAcc, ArgPos, Tail)
- catch
- error:_ ->
- throw({error, {invalid_option_arg, {Name, Arg}}})
- end
- end.
-
-
-%% @doc Find the option for the discrete argument in position specified in the
-%% 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};
-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) ->
- find_non_option_arg(Tail, Pos);
-find_non_option_arg([], _Pos) ->
- false.
-
-
-%% @doc Append options that were not present in the command line arguments with
-%% their default arguments.
--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);
-%% For options with no default argument.
-append_default_options([_Head | Tail], OptAcc) ->
- append_default_options(Tail, OptAcc);
-append_default_options([], OptAcc) ->
- OptAcc.
-
-
-%% @doc Add an option with argument converting it to the data type indicated by the
-%% argument specification.
--spec add_option_with_arg(option_spec(), string(), [option()]) -> [option()].
-add_option_with_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) ->
- case is_valid_arg(ArgSpec, Arg) of
- true ->
- try
- [{Name, to_type(ArgSpec, Arg)} | OptAcc]
- catch
- error:_ ->
- throw({error, {invalid_option_arg, {Name, Arg}}})
- end;
- false ->
- add_option_with_implicit_arg(OptSpec, OptAcc)
- end.
-
-
-%% @doc Add an option with argument that was part of an assignment expression
-%% (e.g. "--verbose=3") converting it to the data type indicated by the
-%% argument specification.
--spec add_option_with_assigned_arg(option_spec(), string(), [option()]) -> [option()].
-add_option_with_assigned_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg, OptAcc) ->
- try
- [{Name, to_type(ArgSpec, Arg)} | OptAcc]
- catch
- error:_ ->
- throw({error, {invalid_option_arg, {Name, Arg}}})
- end.
-
-
-%% @doc Add an option that required an argument but did not have one. Some data
-%% types (boolean, integer) allow implicit or assumed arguments.
--spec add_option_with_implicit_arg(option_spec(), [option()]) -> [option()].
-add_option_with_implicit_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) ->
- case arg_spec_type(ArgSpec) of
- boolean ->
- %% Special case for boolean arguments: if there is no argument we
- %% set the value to 'true'.
- [{Name, true} | OptAcc];
- integer ->
- %% Special case for integer arguments: if the option had not been set
- %% before we set the value to 1. This is needed to support options like
- %% "-v" to return something like {verbose, 1}.
- [{Name, 1} | OptAcc];
- _ ->
- throw({error, {missing_option_arg, Name}})
- end.
-
-
-%% @doc Add an option with an implicit or assumed argument.
--spec add_option_with_implicit_incrementable_arg(option_spec() | arg_spec(), [option()]) -> [option()].
-add_option_with_implicit_incrementable_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) ->
- case arg_spec_type(ArgSpec) of
- boolean ->
- %% Special case for boolean arguments: if there is no argument we
- %% set the value to 'true'.
- [{Name, true} | OptAcc];
- integer ->
- %% Special case for integer arguments: if the option had not been set
- %% before we set the value to 1; if not we increment the previous value
- %% the option had. This is needed to support options like "-vvv" to
- %% return something like {verbose, 3}.
- case OptAcc of
- [{Name, Count} | Tail] ->
- [{Name, Count + 1} | Tail];
- _ ->
- [{Name, 1} | OptAcc]
- end;
- _ ->
- throw({error, {missing_option_arg, Name}})
- end.
-
-
-%% @doc Retrieve the data type form an argument specification.
--spec arg_spec_type(arg_spec()) -> arg_type() | undefined.
-arg_spec_type({Type, _DefaultArg}) ->
- Type;
-arg_spec_type(Type) when is_atom(Type) ->
- Type.
-
-
-%% @doc Convert an argument string to its corresponding data type.
--spec to_type(arg_spec() | arg_type(), string()) -> arg_value().
-to_type({Type, _DefaultArg}, Arg) ->
- to_type(Type, Arg);
-to_type(binary, Arg) ->
- list_to_binary(Arg);
-to_type(atom, Arg) ->
- list_to_atom(Arg);
-to_type(integer, Arg) ->
- list_to_integer(Arg);
-to_type(float, Arg) ->
- list_to_float(Arg);
-to_type(boolean, Arg) ->
- LowerArg = string:to_lower(Arg),
- case is_arg_true(LowerArg) of
- true ->
- true;
- _ ->
- case is_arg_false(LowerArg) of
- true ->
- false;
- false ->
- erlang:error(badarg)
- end
- end;
-to_type(_Type, Arg) ->
- Arg.
-
-
--spec is_arg_true(string()) -> boolean().
-is_arg_true(Arg) ->
- (Arg =:= "true") orelse (Arg =:= "t") orelse
- (Arg =:= "yes") orelse (Arg =:= "y") orelse
- (Arg =:= "on") orelse (Arg =:= "enabled") orelse
- (Arg =:= "1").
-
-
--spec is_arg_false(string()) -> boolean().
-is_arg_false(Arg) ->
- (Arg =:= "false") orelse (Arg =:= "f") orelse
- (Arg =:= "no") orelse (Arg =:= "n") orelse
- (Arg =:= "off") orelse (Arg =:= "disabled") orelse
- (Arg =:= "0").
-
-
--spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean().
-is_valid_arg({Type, _DefaultArg}, Arg) ->
- is_valid_arg(Type, Arg);
-is_valid_arg(boolean, Arg) ->
- is_boolean_arg(Arg);
-is_valid_arg(integer, Arg) ->
- is_non_neg_integer_arg(Arg);
-is_valid_arg(float, Arg) ->
- is_non_neg_float_arg(Arg);
-is_valid_arg(_Type, _Arg) ->
- true.
-
-
--spec is_implicit_arg(arg_spec(), nonempty_string()) -> boolean().
-is_implicit_arg({Type, _DefaultArg}, Arg) ->
- is_implicit_arg(Type, Arg);
-is_implicit_arg(boolean, Arg) ->
- not is_boolean_arg(Arg);
-is_implicit_arg(integer, Arg) ->
- not is_integer_arg(Arg);
-is_implicit_arg(_Type, _Arg) ->
- false.
-
-
--spec is_boolean_arg(string()) -> boolean().
-is_boolean_arg(Arg) ->
- LowerArg = string:to_lower(Arg),
- is_arg_true(LowerArg) orelse is_arg_false(LowerArg).
-
-
--spec is_integer_arg(string()) -> boolean().
-is_integer_arg("-" ++ Tail) ->
- is_non_neg_integer_arg(Tail);
-is_integer_arg(Arg) ->
- is_non_neg_integer_arg(Arg).
-
-
--spec is_non_neg_integer_arg(string()) -> boolean().
-is_non_neg_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 ->
- is_non_neg_integer_arg(Tail);
-is_non_neg_integer_arg([_Head | _Tail]) ->
- false;
-is_non_neg_integer_arg([]) ->
- true.
-
-
--spec is_non_neg_float_arg(string()) -> boolean().
-is_non_neg_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. ->
- is_non_neg_float_arg(Tail);
-is_non_neg_float_arg([_Head | _Tail]) ->
- false;
-is_non_neg_float_arg([]) ->
- true.
-
-
-%% @doc Show a message on standard_error indicating the command line options and
-%% arguments that are supported by the program.
--spec usage([option_spec()], string()) -> ok.
-usage(OptSpecList, ProgramName) ->
- 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)]);
-%% @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).
-
-
-%% @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.
-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)]);
-%% @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).
-
-
-%% @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.
-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]
- end;
-add_option_help(_Opt, _Prefix, Acc) ->
- Acc.
-
-
-
-%% @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;
- _ ->
- T
- end.
diff --git a/src/rebar.erl b/src/rebar.erl
index ded5ebe..36a7b36 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -87,7 +87,9 @@ run(["help"|RawCmds]) when RawCmds =/= [] ->
run(["help"]) ->
help();
run(["info"|_]) ->
- help();
+ %% Catch calls to 'rebar info' to avoid treating plugins' info/2 functions
+ %% as commands.
+ ?CONSOLE("Command 'info' not understood or not applicable~n", []);
run(["version"]) ->
ok = load_rebar_app(),
%% Display vsn and build time info
@@ -178,10 +180,25 @@ run_aux(BaseConfig, Commands) ->
%%
help() ->
OptSpecList = option_spec_list(),
- getopt:usage(OptSpecList, "rebar",
- "[var=value,...] <command,...>",
- [{"var=value", "rebar global variables (e.g. force=1)"},
- {"command", "Command to run (e.g. compile)"}]),
+ rebar_getopt:usage(OptSpecList, "rebar",
+ "[var=value,...] <command,...>",
+ [{"var=value", "rebar global variables (e.g. force=1)"},
+ {"command", "Command to run (e.g. compile)"}]),
+
+ ?CONSOLE("To see a list of built-in commands, execute rebar -c.~n~n", []),
+ ?CONSOLE(
+ "Type 'rebar help <CMD1> <CMD2>' for help on specific commands."
+ "~n~n", []),
+ ?CONSOLE(
+ "rebar allows you to abbreviate the command to run:~n"
+ "$ rebar co # same as rebar compile~n"
+ "$ rebar eu # same as rebar eunit~n"
+ "$ rebar g-d # same as rebar get-deps~n"
+ "$ rebar x eu # same as rebar xref eunit~n"
+ "$ rebar l-d # same as rebar list-deps~n"
+ "$ rebar l-d l-t # same as rebar list-deps list-templates~n"
+ "$ rebar list-d l-te # same as rebar list-deps list-templates~n"
+ "~n", []),
?CONSOLE(
"Core rebar.config options:~n"
" ~p~n"
@@ -212,7 +229,7 @@ help() ->
parse_args(RawArgs) ->
%% Parse getopt options
OptSpecList = option_spec_list(),
- case getopt:parse(OptSpecList, RawArgs) of
+ case rebar_getopt:parse(OptSpecList, RawArgs) of
{ok, Args} ->
Args;
{error, {Reason, Data}} ->
@@ -255,13 +272,27 @@ save_options(Config, {Options, NonOptArgs}) ->
%% set log level based on getopt option
%%
set_log_level(Config, Options) ->
- LogLevel = case proplists:get_all_values(verbose, Options) of
- [] ->
- rebar_log:default_level();
- Verbosities ->
- lists:last(Verbosities)
- end,
- rebar_config:set_global(Config, verbose, LogLevel).
+ {IsVerbose, Level} =
+ case proplists:get_bool(quiet, Options) of
+ true ->
+ {false, rebar_log:error_level()};
+ false ->
+ DefaultLevel = rebar_log:default_level(),
+ case proplists:get_all_values(verbose, Options) of
+ [] ->
+ {false, DefaultLevel};
+ Verbosities ->
+ {true, DefaultLevel + lists:last(Verbosities)}
+ end
+ end,
+
+ case IsVerbose of
+ true ->
+ Config1 = rebar_config:set_xconf(Config, is_verbose, true),
+ rebar_config:set_global(Config1, verbose, Level);
+ false ->
+ rebar_config:set_global(Config, verbose, Level)
+ end.
%%
%% show version information and halt
@@ -314,50 +345,54 @@ show_info_maybe_halt(O, Opts, F) ->
%%
commands() ->
S = <<"
-clean Clean
-compile Compile sources
+clean Clean
+compile Compile sources
-escriptize Generate escript archive
+escriptize Generate escript archive
-create template= [var=foo,...] Create skel based on template and vars
-create-app [appid=myapp] Create simple app skel
-create-node [nodeid=mynode] Create simple node skel
-list-templates List available templates
+create template= [var=foo,...] Create skel based on template and vars
+create-app [appid=myapp] Create simple app skel
+create-lib [libid=mylib] Create simple lib skel
+create-node [nodeid=mynode] Create simple node skel
+list-templates List available templates
-doc Generate Erlang program documentation
+doc Generate Erlang program documentation
-check-deps Display to be fetched dependencies
-get-deps Fetch dependencies
-update-deps Update fetched dependencies
-delete-deps Delete fetched dependencies
-list-deps List dependencies
+check-deps Display to be fetched dependencies
+get-deps Fetch dependencies
+update-deps Update fetched dependencies
+delete-deps Delete fetched dependencies
+list-deps List dependencies
-generate [dump_spec=0/1] Build release with reltool
-overlay Run reltool overlays only
+generate [dump_spec=0/1] Build release with reltool
+overlay Run reltool overlays only
generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files
-eunit [suites=foo] Run eunit tests in foo.erl and
- test/foo_tests.erl
- [suites=foo] [tests=bar] Run specific eunit tests [first test name
- starting with 'bar' in foo.erl and
- test/foo_tests.erl]
- [tests=bar] For every existing suite, run the first
- test whose name starts with bar and, if
- no such test exists, run the test whose
- name starts with bar in the suite's
- _tests module
+eunit [suite[s]=foo] Run EUnit tests in foo.erl and
+ test/foo_tests.erl
+ [suite[s]=foo] [test[s]=bar] Run specific EUnit tests [first test
+ name starting with 'bar' in foo.erl
+ and test/foo_tests.erl]
+ [test[s]=bar] For every existing suite, run the first
+ test whose name starts with bar and, if
+ no such test exists, run the test whose
+ name starts with bar in the suite's
+ _tests module.
+ [random_suite_order=true] Run tests in a random order, either
+ [random_suite_order=Seed] with a random seed for the PRNG, or a
+ specific one.
-ct [suites=] [case=] Run common_test suites
+ct [suite[s]=] [case=] Run common_test suites
-qc Test QuickCheck properties
+qc Test QuickCheck properties
-xref Run cross reference analysis
+xref Run cross reference analysis
-help Show the program options
-version Show version information
+help Show the program options
+version Show version information
">>,
io:put_chars(S).
@@ -372,12 +407,12 @@ option_spec_list() ->
JobsHelp = io_lib:format(
"Number of concurrent workers a command may use. Default: ~B",
[Jobs]),
- VerboseHelp = "Verbosity level (-v, -vv, -vvv, --verbose 3). Default: 0",
[
%% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
{help, $h, "help", undefined, "Show the program options"},
{commands, $c, "commands", undefined, "Show available commands"},
- {verbose, $v, "verbose", integer, VerboseHelp},
+ {verbose, $v, "verbose", integer, "Verbosity level (-v, -vv)"},
+ {quiet, $q, "quiet", boolean, "Quiet, only print error messages"},
{version, $V, "version", undefined, "Show version information"},
{force, $f, "force", undefined, "Force"},
{defines, $D, undefined, string, "Define compiler macro"},
@@ -414,11 +449,33 @@ filter_flags(Config, [Item | Rest], Commands) ->
end.
command_names() ->
- ["check-deps", "clean", "compile", "create", "create-app", "create-node",
- "ct", "delete-deps", "doc", "eunit", "escriptize", "generate",
- "generate-appups", "generate-upgrade", "get-deps", "help", "list-deps",
- "list-templates", "qc", "update-deps", "overlay", "shell", "version",
- "xref"].
+ [
+ "check-deps",
+ "clean",
+ "compile",
+ "create",
+ "create-app",
+ "create-lib",
+ "create-node",
+ "ct",
+ "delete-deps",
+ "doc",
+ "eunit",
+ "escriptize",
+ "generate",
+ "generate-appups",
+ "generate-upgrade",
+ "get-deps",
+ "help",
+ "list-deps",
+ "list-templates",
+ "qc",
+ "update-deps",
+ "overlay",
+ "shell",
+ "version",
+ "xref"
+ ].
unabbreviate_command_names([]) ->
[];
diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl
index 8158eb6..a2484e1 100644
--- a/src/rebar_app_utils.erl
+++ b/src/rebar_app_utils.erl
@@ -163,15 +163,6 @@ consult_app_file(Filename) ->
false ->
file:consult(Filename);
true ->
- %% TODO: EXPERIMENTAL For now let's warn the user if a
- %% script is going to be run.
- case filelib:is_regular([Filename, ".script"]) of
- true ->
- ?CONSOLE("NOTICE: Using experimental *.app.src.script "
- "functionality on ~s ~n", [Filename]);
- _ ->
- ok
- end,
rebar_config:consult_file(Filename)
end.
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index a0dec30..1957070 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -49,7 +49,7 @@ run(Config, FirstFiles, RestFiles, CompileFn) ->
Jobs = rebar:get_jobs(Config),
?DEBUG("Starting ~B compile worker(s)~n", [Jobs]),
Pids = [spawn_monitor(F) || _I <- lists:seq(1,Jobs)],
- compile_queue(Pids, RestFiles)
+ compile_queue(Config, Pids, RestFiles)
end.
run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
@@ -139,27 +139,31 @@ compile_each([Source | Rest], Config, CompileFn) ->
skipped ->
?INFO("Skipped ~s\n", [Source]);
Error ->
+ ?CONSOLE("Compiling ~s failed:\n",
+ [maybe_absname(Config, Source)]),
maybe_report(Error),
?DEBUG("Compilation failed: ~p\n", [Error]),
?FAIL
end,
compile_each(Rest, Config, CompileFn).
-compile_queue([], []) ->
+compile_queue(_Config, [], []) ->
ok;
-compile_queue(Pids, Targets) ->
+compile_queue(Config, Pids, Targets) ->
receive
{next, Worker} ->
case Targets of
[] ->
Worker ! empty,
- compile_queue(Pids, Targets);
+ compile_queue(Config, Pids, Targets);
[Source | Rest] ->
Worker ! {compile, Source},
- compile_queue(Pids, Rest)
+ compile_queue(Config, Pids, Rest)
end;
- {fail, Error} ->
+ {fail, {_, {source, Source}}=Error} ->
+ ?CONSOLE("Compiling ~s failed:\n",
+ [maybe_absname(Config, Source)]),
maybe_report(Error),
?DEBUG("Worker compilation failed: ~p\n", [Error]),
?FAIL;
@@ -167,20 +171,20 @@ compile_queue(Pids, Targets) ->
{compiled, Source, Warnings} ->
report(Warnings),
?CONSOLE("Compiled ~s\n", [Source]),
- compile_queue(Pids, Targets);
+ compile_queue(Config, Pids, Targets);
{compiled, Source} ->
?CONSOLE("Compiled ~s\n", [Source]),
- compile_queue(Pids, Targets);
+ compile_queue(Config, Pids, Targets);
{skipped, Source} ->
?INFO("Skipped ~s\n", [Source]),
- compile_queue(Pids, Targets);
+ compile_queue(Config, Pids, Targets);
{'DOWN', Mref, _, Pid, normal} ->
?DEBUG("Worker exited cleanly\n", []),
Pids2 = lists:delete({Pid, Mref}, Pids),
- compile_queue(Pids2, Targets);
+ compile_queue(Config, Pids2, Targets);
{'DOWN', _Mref, _, _Pid, Info} ->
?DEBUG("Worker failed: ~p\n", [Info]),
@@ -202,8 +206,7 @@ compile_worker(QueuePid, Config, CompileFn) ->
QueuePid ! {skipped, Source},
compile_worker(QueuePid, Config, CompileFn);
Error ->
- QueuePid ! {fail, [{error, Error},
- {source, Source}]},
+ QueuePid ! {fail, {{error, Error}, {source, Source}}},
ok
end;
@@ -224,7 +227,7 @@ format_warnings(Config, Source, Warnings, Opts) ->
end,
format_errors(Config, Source, Prefix, Warnings).
-maybe_report([{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}]) ->
+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])]);
@@ -239,12 +242,7 @@ report(Messages) ->
format_errors(Config, _MainSource, Extra, Errors) ->
[begin
- AbsSource = case rebar_utils:processing_base_dir(Config) of
- true ->
- Source;
- false ->
- filename:absname(Source)
- end,
+ AbsSource = maybe_absname(Config, Source),
[format_error(AbsSource, Extra, Desc) || Desc <- Descs]
end
|| {Source, Descs} <- Errors].
@@ -258,3 +256,11 @@ format_error(AbsSource, Extra, {Line, Mod, Desc}) ->
format_error(AbsSource, Extra, {Mod, Desc}) ->
ErrorDesc = Mod:format_error(Desc),
?FMT("~s: ~s~s~n", [AbsSource, Extra, ErrorDesc]).
+
+maybe_absname(Config, Filename) ->
+ case rebar_utils:processing_base_dir(Config) of
+ true ->
+ Filename;
+ false ->
+ filename:absname(Filename)
+ end.
diff --git a/src/rebar_config.erl b/src/rebar_config.erl
index 461de5d..9b58d4f 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -31,7 +31,6 @@
get_all/2,
set/3,
set_global/3, get_global/3,
- is_verbose/1,
save_env/3, get_env/2, reset_envs/1,
set_skip_dir/2, is_skip_dir/2, reset_skip_dirs/1,
clean_config/2,
@@ -110,10 +109,6 @@ get_global(Config, Key, Default) ->
Value
end.
-is_verbose(Config) ->
- DefaulLevel = rebar_log:default_level(),
- get_global(Config, verbose, DefaulLevel) > DefaulLevel.
-
consult_file(File) ->
case filename:extension(File) of
".script" ->
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index 3172d64..4efc978 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -163,6 +163,13 @@ skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
Dir, Command, DirSet) ->
case rebar_app_utils:is_skipped_app(Config, AppFile) of
+ {Config1, {true, _SkippedApp}} when Command == 'update-deps' ->
+ %% update-deps does its own app skipping. Unfortunately there's no
+ %% way to signal this to rebar_core, so we have to explicitly do it
+ %% here... Otherwise if you use app=, it'll skip the toplevel
+ %% directory and nothing will be updated.
+ process_dir1(Dir, Command, DirSet, Config1,
+ CurrentCodePath, ModuleSet);
{Config1, {true, SkippedApp}} ->
?DEBUG("Skipping app: ~p~n", [SkippedApp]),
Config2 = increment_operations(Config1),
@@ -172,8 +179,9 @@ skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
CurrentCodePath, ModuleSet)
end.
-process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
+process_dir1(Dir, Command, DirSet, Config, CurrentCodePath,
{DirModules, ModuleSetFile}) ->
+ Config0 = rebar_config:set_xconf(Config, current_command, Command),
%% Get the list of modules for "any dir". This is a catch-all list
%% of modules that are processed in addition to modules associated
%% with this directory type. These any_dir modules are processed
@@ -391,18 +399,19 @@ update_code_path(Config) ->
[] ->
no_change;
Paths ->
- OldPath = code:get_path(),
LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []),
ok = code:add_pathsa(LibPaths),
- {old, OldPath}
+ %% track just the paths we added, so we can remove them without
+ %% removing other paths added by this dep
+ {added, LibPaths}
end.
restore_code_path(no_change) ->
ok;
-restore_code_path({old, Path}) ->
+restore_code_path({added, Paths}) ->
%% Verify that all of the paths still exist -- some dynamically
%% added paths can get blown away during clean.
- true = code:set_path([F || F <- Path, erl_prim_loader_is_file(F)]),
+ _ = [code:del_path(F) || F <- Paths, erl_prim_loader_is_file(F)],
ok.
erl_prim_loader_is_file(File) ->
@@ -412,7 +421,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).
@@ -490,6 +502,8 @@ acc_modules([Module | Rest], Command, Config, File, Acc) ->
%%
plugin_modules(Config, PredirsAssoc) ->
Modules = lists:flatten(rebar_config:get_all(Config, plugins)),
+ ?DEBUG("Plugins requested while processing ~s: ~p~n",
+ [rebar_utils:get_cwd(), Modules]),
plugin_modules(Config, PredirsAssoc, ulist(Modules)).
ulist(L) ->
@@ -530,21 +544,8 @@ plugin_modules(Config, PredirsAssoc, FoundModules, MissingModules) ->
load_plugin_modules(Config, PredirsAssoc, Modules) ->
Cwd = rebar_utils:get_cwd(),
- PluginDirs = 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.
- [
- filename:join(Dir, "plugins") ||
- Dir <- get_plugin_base_dirs(Cwd, PredirsAssoc)
- ],
+ PluginDirs = get_all_plugin_dirs(Config, Cwd, PredirsAssoc),
+ ?DEBUG("Plugin dirs for ~s:~n~p~n", [Cwd, PluginDirs]),
%% Find relevant sources in base_dir and plugin_dir
Erls = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"),
@@ -559,12 +560,34 @@ load_plugin_modules(Config, PredirsAssoc, Modules) ->
NotLoaded = [V || V <- Modules, FilterMissing(V)],
{Loaded, NotLoaded}.
-%% @doc PredirsAssoc is a dictionary of plugindir -> 'parent' pairs
+get_all_plugin_dirs(Config, Cwd, PredirsAssoc) ->
+ [rebar_utils:get_cwd()]
+ ++ 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].
+ 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 9951f8e..f3ed29f 100644
--- a/src/rebar_ct.erl
+++ b/src/rebar_ct.erl
@@ -101,14 +101,15 @@ run_test(TestDir, LogDir, Config, _File) ->
{Cmd, RawLog} = make_cmd(TestDir, LogDir, Config),
?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
clear_log(LogDir, RawLog),
- Output = case rebar_config:is_verbose(Config) of
+ Output = case rebar_log:is_verbose(Config) of
false ->
" >> " ++ RawLog ++ " 2>&1";
true ->
" 2>&1 | tee -a " ++ RawLog
end,
- case rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}, return_on_error]) of
+ 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!
@@ -135,15 +136,20 @@ clear_log(LogDir, RawLog) ->
check_success_log(Config, RawLog) ->
check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
-check_fail_log(Config, RawLog, Command, {Rc, Output}) ->
- check_log(Config, RawLog, fun(_Msg) ->
+-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).
+ 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}' "
+ rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" "
++ RawLog, [{use_stdout, false}]),
MakeFailed = string:str(Msg, "{error,make_failed}") =/= 0,
RunFailed = string:str(Msg, ", 0 failed") =:= 0,
@@ -166,7 +172,7 @@ check_log(Config,RawLog,Fun) ->
%% Show the log if it hasn't already been shown because verbose was on
show_log(Config, RawLog) ->
?CONSOLE("Showing log\n", []),
- case rebar_config:is_verbose(Config) of
+ case rebar_log:is_verbose(Config) of
false ->
{ok, Contents} = file:read_file(RawLog),
?CONSOLE("~s", [Contents]);
@@ -186,6 +192,15 @@ make_cmd(TestDir, RawLogDir, Config) ->
""
end,
+ %% Check for the availability of ct_run; if we can't find it, generate a
+ %% warning and use the old school, less reliable approach to running CT.
+ BaseCmd = case os:find_executable("ct_run") of
+ false ->
+ "erl -noshell -s ct_run script_start -s erlang halt";
+ _ ->
+ "ct_run -noshell"
+ end,
+
%% Add the code path of the rebar process to the code path. This
%% includes the dependencies in the code path. The directories
%% that are part of the root Erlang install are filtered out to
@@ -197,14 +212,15 @@ make_cmd(TestDir, RawLogDir, Config) ->
CodePathString = string:join(CodeDirs, " "),
Cmd = case get_ct_specs(Cwd) of
undefined ->
- ?FMT("erl " % should we expand ERL_PATH?
- " -noshell -pa ~s ~s"
+ ?FMT("~s"
+ " -pa ~s"
+ " ~s"
" ~s"
" -logdir \"~s\""
" -env TEST_DIR \"~s\""
- " ~s"
- " -s ct_run script_start -s erlang halt",
- [CodePathString,
+ " ~s",
+ [BaseCmd,
+ CodePathString,
Include,
build_name(Config),
LogDir,
@@ -216,14 +232,15 @@ make_cmd(TestDir, RawLogDir, Config) ->
get_suites(Config, TestDir) ++
get_case(Config);
SpecFlags ->
- ?FMT("erl " % should we expand ERL_PATH?
- " -noshell -pa ~s ~s"
+ ?FMT("~s"
+ " -pa ~s"
+ " ~s"
" ~s"
" -logdir \"~s\""
" -env TEST_DIR \"~s\""
- " ~s"
- " -s ct_run script_start -s erlang halt",
- [CodePathString,
+ " ~s",
+ [BaseCmd,
+ CodePathString,
Include,
build_name(Config),
LogDir,
@@ -303,7 +320,7 @@ get_config_file(TestDir) ->
end.
get_suites(Config, TestDir) ->
- case rebar_config:get_global(Config, suites, undefined) of
+ case get_suites(Config) of
undefined ->
" -dir " ++ TestDir;
Suites ->
@@ -312,6 +329,14 @@ get_suites(Config, TestDir) ->
string:join([" -suite"] ++ Suites2, " ")
end.
+get_suites(Config) ->
+ case rebar_config:get_global(Config, suites, undefined) of
+ undefined ->
+ rebar_config:get_global(Config, suite, undefined);
+ Suites ->
+ Suites
+ end.
+
find_suite_path(Suite, TestDir) ->
Path = filename:join(TestDir, Suite ++ "_SUITE.erl"),
case filelib:is_regular(Path) of
diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl
index 313deaa..43bde04 100644
--- a/src/rebar_deps.erl
+++ b/src/rebar_deps.erl
@@ -68,37 +68,57 @@ preprocess(Config, _) ->
%% Add available deps to code path
Config3 = update_deps_code_path(Config2, AvailableDeps),
- %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core so that
- %% the current command doesn't run on the dep dir. However, pre/postprocess
- %% WILL run (and we want it to) for transitivity purposes.
- %%
- %% Also, if skip_deps=comma,separated,app,list, then only the given
- %% dependencies are skipped.
- NewConfig = case rebar_config:get_global(Config3, skip_deps, false) of
- "true" ->
- lists:foldl(
- fun(#dep{dir = Dir}, C) ->
- rebar_config:set_skip_dir(C, Dir)
- end, Config3, AvailableDeps);
- Apps when is_list(Apps) ->
- SkipApps = [list_to_atom(App) || App <- string:tokens(Apps, ",")],
- lists:foldl(
- fun(#dep{dir = Dir, app = App}, C) ->
- case lists:member(App, SkipApps) of
- true -> rebar_config:set_skip_dir(C, Dir);
- false -> C
- end
- end, Config3, AvailableDeps);
- _ ->
- Config3
- end,
-
%% Filtering out 'raw' dependencies so that no commands other than
%% deps-related can be executed on their directories.
NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw],
- %% Return all the available dep directories for process
- {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}.
+ case rebar_config:get_xconf(Config, current_command, undefined) of
+ 'update-deps' ->
+ %% Skip ALL of the dep folders, we do this because we don't want
+ %% any other calls to preprocess() for update-deps beyond the
+ %% toplevel directory. They aren't actually harmful, but they slow
+ %% things down unnecessarily.
+ NewConfig = lists:foldl(
+ fun(D, Acc) ->
+ rebar_config:set_skip_dir(Acc, D#dep.dir)
+ end,
+ Config3,
+ collect_deps(rebar_utils:get_cwd(), Config3)),
+ %% Return the empty list, as we don't want anything processed before
+ %% us.
+ {ok, NewConfig, []};
+ _ ->
+ %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core
+ %% so that the current command doesn't run on the dep dir.
+ %% However, pre/postprocess WILL run (and we want it to) for
+ %% transitivity purposes.
+ %%
+ %% Also, if skip_deps=comma,separated,app,list, then only the given
+ %% dependencies are skipped.
+ NewConfig =
+ case rebar_config:get_global(Config3, skip_deps, false) of
+ "true" ->
+ lists:foldl(
+ fun(#dep{dir = Dir}, C) ->
+ rebar_config:set_skip_dir(C, Dir)
+ end, Config3, AvailableDeps);
+ Apps when is_list(Apps) ->
+ SkipApps = [list_to_atom(App) ||
+ App <- string:tokens(Apps, ",")],
+ lists:foldl(
+ fun(#dep{dir = Dir, app = App}, C) ->
+ case lists:member(App, SkipApps) of
+ true -> rebar_config:set_skip_dir(C, Dir);
+ false -> C
+ end
+ end, Config3, AvailableDeps);
+ _ ->
+ Config3
+ end,
+
+ %% Return all the available dep directories for process
+ {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}
+ end.
postprocess(Config, _) ->
case rebar_config:get_xconf(Config, ?MODULE, undefined) of
@@ -169,17 +189,24 @@ do_check_deps(Config) ->
{ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}.
'update-deps'(Config, _) ->
- %% Determine what deps are required
- RawDeps = rebar_config:get_local(Config, deps, []),
- {Config1, Deps} = find_deps(Config, read, RawDeps),
-
- %% Update each dep
- UpdatedDeps = [update_source(Config1, D)
- || D <- Deps, D#dep.source =/= undefined],
+ Config1 = rebar_config:set_xconf(Config, depowner, dict:new()),
+ {Config2, UpdatedDeps} = update_deps_int(Config1, []),
+ DepOwners = rebar_config:get_xconf(Config2, depowner, dict:new()),
+
+ %% check for conflicting deps
+ _ = [?ERROR("Conflicting dependencies for ~p: ~p~n",
+ [K, [{"From: " ++ string:join(dict:fetch(D, DepOwners), ", "),
+ {D#dep.vsn_regex, D#dep.source}} || D <- V]])
+ || {K, V} <- dict:to_list(
+ lists:foldl(
+ fun(Dep, Acc) ->
+ dict:append(Dep#dep.app, Dep, Acc)
+ end, dict:new(), UpdatedDeps)),
+ length(V) > 1],
%% Add each updated dep to our list of dirs for post-processing. This yields
%% the necessary transitivity of the deps
- {ok, save_dep_dirs(Config1, UpdatedDeps)}.
+ {ok, save_dep_dirs(Config, UpdatedDeps)}.
'delete-deps'(Config, _) ->
%% Delete all the available deps in our deps/ directory, if any
@@ -230,21 +257,42 @@ info_help(Description) ->
[
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]}]}
+ {deps,
+ [app_name,
+ {rebar, "1.0.*"},
+ {rebar, ".*",
+ {git, "git://github.com/rebar/rebar.git"}},
+ {rebar, ".*",
+ {git, "git://github.com/rebar/rebar.git", "Rev"}},
+ {rebar, "1.0.*",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}}},
+ {rebar, "1.0.0",
+ {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}},
+ {rebar, "",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}},
+ [raw]},
+ {app_name, ".*", {hg, "https://www.example.org/url"}},
+ {app_name, ".*", {rsync, "Url"}},
+ {app_name, ".*", {svn, "https://www.example.org/url"}},
+ {app_name, ".*", {svn, "svn://svn.example.org/url"}},
+ {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}},
+ {app_name, ".*", {fossil, "https://www.example.org/url"}},
+ {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}]}
]).
%% 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"),
+ GlobalDepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir),
+ DepsDir = case os:getenv("REBAR_DEPS_DIR") of
+ false ->
+ GlobalDepsDir;
+ Dir ->
+ Dir
+ end,
rebar_config:set_xconf(Config, deps_dir, DepsDir);
set_shared_deps_dir(Config, _DepsDir) ->
Config.
@@ -256,7 +304,7 @@ get_deps_dir(Config) ->
get_deps_dir(Config, "").
get_deps_dir(Config, App) ->
- BaseDir = rebar_config:get_xconf(Config, base_dir, []),
+ BaseDir = rebar_utils:base_dir(Config),
DepsDir = get_shared_deps_dir(Config, "deps"),
{true, filename:join([BaseDir, DepsDir, App])}.
@@ -307,7 +355,8 @@ find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc);
find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc);
-find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc) when is_list(Opts) ->
+find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc)
+ when is_list(Opts) ->
Dep = #dep { app = App,
vsn_regex = VsnRegex,
source = Source,
@@ -411,7 +460,8 @@ is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) ->
{Config, {false, {missing_app_file, Path}}}
end;
is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) ->
- ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n", [App, Path]),
+ ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n",
+ [App, Path]),
case filelib:is_dir(Path) of
true ->
%% TODO: look for version string in <Path>/VERSION file? Not clear
@@ -434,8 +484,8 @@ use_source(Config, Dep, Count) ->
case filelib:is_dir(Dep#dep.dir) of
true ->
%% Already downloaded -- verify the versioning matches the regex
- case is_app_available(Config, Dep#dep.app,
- Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
+ case is_app_available(Config, Dep#dep.app, Dep#dep.vsn_regex,
+ Dep#dep.dir, Dep#dep.is_raw) of
{Config1, {true, _}} ->
Dir = filename:join(Dep#dep.dir, "ebin"),
ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
@@ -496,8 +546,6 @@ download_source(AppDir, {rsync, Url}) ->
rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []);
download_source(AppDir, {fossil, Url}) ->
download_source(AppDir, {fossil, Url, ""});
-download_source(AppDir, {fossil, Url, latest}) ->
- download_source(AppDir, {fossil, Url, ""});
download_source(AppDir, {fossil, Url, Version}) ->
Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"),
ok = filelib:ensure_dir(Repository),
@@ -532,10 +580,12 @@ update_source1(AppDir, {git, Url, ""}) ->
update_source1(AppDir, {git, _Url, {branch, Branch}}) ->
ShOpts = [{cd, AppDir}],
rebar_utils:sh("git fetch origin", ShOpts),
- rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), ShOpts);
+ rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts),
+ rebar_utils:sh(
+ ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts);
update_source1(AppDir, {git, _Url, {tag, Tag}}) ->
ShOpts = [{cd, AppDir}],
- rebar_utils:sh("git fetch --tags origin", ShOpts),
+ rebar_utils:sh("git fetch origin", ShOpts),
rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts);
update_source1(AppDir, {git, _Url, Refspec}) ->
ShOpts = [{cd, AppDir}],
@@ -551,13 +601,90 @@ update_source1(AppDir, {rsync, Url}) ->
rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]);
update_source1(AppDir, {fossil, Url}) ->
update_source1(AppDir, {fossil, Url, ""});
-update_source1(AppDir, {fossil, Url, latest}) ->
- update_source1(AppDir, {fossil, Url, ""});
update_source1(AppDir, {fossil, _Url, Version}) ->
ok = file:set_cwd(AppDir),
rebar_utils:sh("fossil pull", [{cd, AppDir}]),
rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).
+%% Recursively update deps, this is not done via rebar's usual dep traversal as
+%% that is the wrong order (tips are updated before branches). Instead we do a
+%% traverse the deps at each level completely before traversing *their* deps.
+%% This allows updates to actually propogate down the tree, rather than fail to
+%% flow up the tree, which was the previous behaviour.
+update_deps_int(Config0, UDD) ->
+ %% Determine what deps are required
+ ConfDir = filename:basename(rebar_utils:get_cwd()),
+ RawDeps = rebar_config:get_local(Config0, deps, []),
+ {Config1, Deps} = find_deps(Config0, read, RawDeps),
+
+ %% Update each dep
+ UpdatedDeps = [update_source(Config1, D)
+ || D <- Deps, D#dep.source =/= undefined,
+ not lists:member(D, UDD),
+ not should_skip_update_dep(Config1, D)
+ ],
+
+ lists:foldl(fun(Dep, {Config, Updated}) ->
+ {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
+ Config2 = case has_vcs_dir(element(1, Dep#dep.source),
+ AppDir) of
+ false ->
+ %% If the dep did not exist (maybe it
+ %% was added), clone it.
+ %% We'll traverse ITS deps below and
+ %% clone them if needed.
+ {C1, _D1} = use_source(Config, Dep),
+ C1;
+ true ->
+ Config
+ end,
+ ok = file:set_cwd(AppDir),
+ Config3 = rebar_config:new(Config2),
+ %% track where a dep comes from...
+ DepOwner = dict:append(
+ Dep, ConfDir,
+ rebar_config:get_xconf(Config3, depowner,
+ dict:new())),
+ Config4 = rebar_config:set_xconf(Config3, depowner,
+ DepOwner),
+
+ {Config5, Res} = update_deps_int(Config4, Updated),
+ {Config5, lists:umerge(lists:sort(Res),
+ lists:sort(Updated))}
+ end, {Config1, lists:umerge(lists:sort(UpdatedDeps),
+ lists:sort(UDD))}, UpdatedDeps).
+
+should_skip_update_dep(Config, Dep) ->
+ {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
+ case rebar_app_utils:is_app_dir(AppDir) of
+ false ->
+ false;
+ {true, AppFile} ->
+ case rebar_app_utils:is_skipped_app(Config, AppFile) of
+ {_Config, {true, _SkippedApp}} ->
+ true;
+ _ ->
+ false
+ end
+ end.
+
+%% Recursively walk the deps and build a list of them.
+collect_deps(Dir, C) ->
+ case file:set_cwd(Dir) of
+ ok ->
+ Config = rebar_config:new(C),
+ RawDeps = rebar_config:get_local(Config, deps, []),
+ {Config1, Deps} = find_deps(Config, read, RawDeps),
+
+ lists:flatten(Deps ++ [begin
+ {true, AppDir} = get_deps_dir(
+ Config1, Dep#dep.app),
+ collect_deps(AppDir, C)
+ end || Dep <- Deps]);
+ _ ->
+ []
+ end.
+
%% ===================================================================
%% Source helper functions
diff --git a/src/rebar_dia_compiler.erl b/src/rebar_dia_compiler.erl
index f81c734..ba9d159 100644
--- a/src/rebar_dia_compiler.erl
+++ b/src/rebar_dia_compiler.erl
@@ -75,8 +75,8 @@ compile_dia(Source, Target, Config) ->
case diameter_dict_util:parse({path, Source}, []) of
{ok, Spec} ->
FileName = dia_filename(Source, Spec),
- diameter_codegen:from_dict(FileName, Spec, Opts, erl),
- diameter_codegen:from_dict(FileName, Spec, Opts, hrl),
+ _ = diameter_codegen:from_dict(FileName, Spec, Opts, erl),
+ _ = diameter_codegen:from_dict(FileName, Spec, Opts, hrl),
HrlFile = filename:join("src", FileName ++ ".hrl"),
case filelib:is_regular(HrlFile) of
true ->
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index caef0d2..a1740b0 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -34,6 +34,18 @@
info/2]).
-include("rebar.hrl").
+-include_lib("stdlib/include/erl_compile.hrl").
+
+-define(ERLCINFO_VSN, 1).
+-define(ERLCINFO_FILE, "erlcinfo").
+-type erlc_info_v() :: {digraph:vertex(), term()} | 'false'.
+-type erlc_info_e() :: {digraph:vertex(), digraph:vertex()}.
+-type erlc_info() :: {list(erlc_info_v()), list(erlc_info_e())}.
+-record(erlcinfo,
+ {
+ vsn = ?ERLCINFO_VSN :: pos_integer(),
+ info = {[], []} :: erlc_info()
+ }).
%% ===================================================================
%% Public API
@@ -89,7 +101,7 @@ compile(Config, _AppFile) ->
doterl_compile(Config, "ebin").
-spec clean(rebar_config:config(), file:filename()) -> 'ok'.
-clean(_Config, _AppFile) ->
+clean(Config, _AppFile) ->
MibFiles = rebar_utils:find_files("mibs", "^.*\\.mib\$"),
MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
rebar_file_utils:delete_each(
@@ -102,6 +114,9 @@ clean(_Config, _AppFile) ->
[ binary_to_list(iolist_to_binary(re:replace(F, "\\.[x|y]rl$", ".erl")))
|| F <- YrlFiles ]),
+ %% Delete the build graph, if any
+ rebar_file_utils:rm_rf(erlcinfo_file(Config)),
+
%% Erlang compilation is recursive, so it's possible that we have a nested
%% directory structure in ebin with .beam files within. As such, we want
%% to scan whatever is left in the ebin/ directory for sub-dirs which
@@ -156,7 +171,8 @@ test_compile(Config, Cmd, OutDir) ->
%% Compile erlang code to OutDir, using a tweaked config
%% with appropriate defines for eunit, and include all the test modules
%% as well.
- ok = doterl_compile(test_compile_config(Config, Cmd), OutDir, TestErls),
+ ok = doterl_compile(test_compile_config(Config, ErlOpts, Cmd),
+ OutDir, TestErls),
{ok, SrcErls}.
@@ -200,12 +216,11 @@ info_help(Description) ->
{yrl_first_files, []}
]).
-test_compile_config(Config, Cmd) ->
+test_compile_config(Config, ErlOpts, Cmd) ->
{Config1, TriqOpts} = triq_opts(Config),
{Config2, PropErOpts} = proper_opts(Config1),
{Config3, EqcOpts} = eqc_opts(Config2),
- ErlOpts = rebar_config:get_list(Config3, erl_opts, []),
OptsAtom = list_to_atom(Cmd ++ "_compile_opts"),
EunitOpts = rebar_config:get_list(Config3, OptsAtom, []),
Opts0 = [{d, 'TEST'}] ++
@@ -259,7 +274,7 @@ doterl_compile(Config, OutDir) ->
doterl_compile(Config, OutDir, []).
doterl_compile(Config, OutDir, MoreSources) ->
- FirstErls = rebar_config:get_list(Config, erl_first_files, []),
+ ErlFirstFiles = rebar_config:get_list(Config, erl_first_files, []),
ErlOpts = rebar_utils:erl_opts(Config),
?DEBUG("erl_opts ~p~n", [ErlOpts]),
%% Support the src_dirs option allowing multiple directories to
@@ -267,114 +282,270 @@ doterl_compile(Config, OutDir, MoreSources) ->
%% eunit tests be separated from the core application source.
SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
RestErls = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources,
- not lists:member(Source, FirstErls)],
-
- %% Split RestErls so that parse_transforms and behaviours are instead added
- %% to erl_first_files, parse transforms first.
- %% This should probably be somewhat combined with inspect_epp
- [ParseTransforms, Behaviours, OtherErls] =
- lists:foldl(fun(F, [A, B, C]) ->
- case compile_priority(F) of
- parse_transform ->
- [[F | A], B, C];
- behaviour ->
- [A, [F | B], C];
- callback ->
- [A, [F | B], C];
- _ ->
- [A, B, [F | C]]
- end
- end, [[], [], []], RestErls),
-
- NewFirstErls = FirstErls ++ ParseTransforms ++ Behaviours,
-
+ not lists:member(Source, ErlFirstFiles)],
%% Make sure that ebin/ exists and is on the path
ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")),
CurrPath = code:get_path(),
true = code:add_path(filename:absname("ebin")),
OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir),
- rebar_base_compiler:run(Config, NewFirstErls, OtherErls,
- fun(S, C) ->
- internal_erl_compile(C, S, OutDir1, ErlOpts)
- end),
+ G = init_erlcinfo(Config, RestErls),
+ %% Split RestErls so that files which are depended on are treated
+ %% like erl_first_files.
+ {OtherFirstErls, OtherErls} =
+ lists:partition(
+ fun(F) ->
+ Children = get_children(G, F),
+ log_files(?FMT("Files dependent on ~s", [F]), Children),
+
+ case erls(Children) of
+ [] ->
+ %% There are no files dependent on this file.
+ false;
+ _ ->
+ %% There are some files dependent on the file.
+ %% Thus the file has higher priority
+ %% and should be compiled in the first place.
+ true
+ end
+ end, RestErls),
+ %% Dependencies of OtherFirstErls that must be compiled first.
+ OtherFirstErlsDeps = lists:flatmap(
+ fun(Erl) -> erls(get_parents(G, Erl)) end,
+ OtherFirstErls),
+ %% NOTE: In case the way we retrieve OtherFirstErlsDeps or merge
+ %% it with OtherFirstErls does not result in the correct compile
+ %% priorities, or the method in use proves to be too slow for
+ %% certain projects, consider using a more elaborate method (maybe
+ %% digraph_utils) or alternatively getting and compiling the .erl
+ %% parents of an individual Source in internal_erl_compile. By not
+ %% handling this in internal_erl_compile, we also avoid extra
+ %% needs_compile/2 calls.
+ FirstErls = ErlFirstFiles ++ uo_merge(OtherFirstErlsDeps, OtherFirstErls),
+ ?DEBUG("Files to compile first: ~p~n", [FirstErls]),
+ rebar_base_compiler:run(
+ Config, FirstErls, OtherErls,
+ fun(S, C) ->
+ internal_erl_compile(C, S, OutDir1, ErlOpts, G)
+ end),
true = code:set_path(CurrPath),
ok.
+%%
+%% Return all .erl files from a list of files
+%%
+erls(Files) ->
+ [Erl || Erl <- Files, filename:extension(Erl) =:= ".erl"].
+
+%%
+%% Return a list without duplicates while preserving order
+%%
+ulist(L) ->
+ ulist(L, []).
+
+ulist([H|T], Acc) ->
+ case lists:member(H, T) of
+ true ->
+ ulist(T, Acc);
+ false ->
+ ulist(T, [H|Acc])
+ end;
+ulist([], Acc) ->
+ lists:reverse(Acc).
+
+%%
+%% Merge two lists without duplicates while preserving order
+%%
+uo_merge(L1, L2) ->
+ lists:foldl(fun(E, Acc) -> u_add_element(E, Acc) end, ulist(L1), L2).
+
+u_add_element(Elem, [Elem|_]=Set) -> Set;
+u_add_element(Elem, [E1|Set]) -> [E1|u_add_element(Elem, Set)];
+u_add_element(Elem, []) -> [Elem].
+
-spec include_path(file:filename(),
rebar_config:config()) -> [file:filename(), ...].
include_path(Source, Config) ->
ErlOpts = rebar_config:get(Config, erl_opts, []),
- ["include", filename:dirname(Source)]
- ++ proplists:get_all_values(i, ErlOpts).
-
--spec inspect(file:filename(),
- [file:filename(), ...]) -> {string(), [string()]}.
-inspect(Source, IncludePath) ->
- ModuleDefault = filename:basename(Source, ".erl"),
- case epp:open(Source, IncludePath) of
- {ok, Epp} ->
- inspect_epp(Epp, Source, ModuleDefault, []);
- {error, Reason} ->
- ?DEBUG("Failed to inspect ~s: ~p\n", [Source, Reason]),
- {ModuleDefault, []}
- end.
-
--spec inspect_epp(pid(), file:filename(), file:filename(),
- [string()]) -> {string(), [string()]}.
-inspect_epp(Epp, Source, Module, Includes) ->
- case epp:parse_erl_form(Epp) of
- {ok, {attribute, _, module, ModInfo}} ->
- ActualModuleStr =
- case ModInfo of
- %% Typical module name, single atom
- ActualModule when is_atom(ActualModule) ->
- atom_to_list(ActualModule);
- %% Packag-ized module name, list of atoms
- ActualModule when is_list(ActualModule) ->
- string:join([atom_to_list(P) ||
- P <- ActualModule], ".");
- %% Parameterized module name, single atom
- {ActualModule, _} when is_atom(ActualModule) ->
- atom_to_list(ActualModule);
- %% Parameterized and packagized module name, list of atoms
- {ActualModule, _} when is_list(ActualModule) ->
- string:join([atom_to_list(P) ||
- P <- ActualModule], ".")
- end,
- inspect_epp(Epp, Source, ActualModuleStr, Includes);
- {ok, {attribute, 1, file, {Module, 1}}} ->
- inspect_epp(Epp, Source, Module, Includes);
- {ok, {attribute, 1, file, {Source, 1}}} ->
- inspect_epp(Epp, Source, Module, Includes);
- {ok, {attribute, 1, file, {IncFile, 1}}} ->
- inspect_epp(Epp, Source, Module, [IncFile | Includes]);
- {eof, _} ->
- epp:close(Epp),
- {Module, Includes};
- _ ->
- inspect_epp(Epp, Source, Module, Includes)
- end.
+ lists:usort(["include", filename:dirname(Source)]
+ ++ proplists:get_all_values(i, ErlOpts)).
-spec needs_compile(file:filename(), file:filename(),
[string()]) -> boolean().
-needs_compile(Source, Target, Hrls) ->
+needs_compile(Source, Target, Parents) ->
TargetLastMod = filelib:last_modified(Target),
lists:any(fun(I) -> TargetLastMod < filelib:last_modified(I) end,
- [Source] ++ Hrls).
+ [Source] ++ Parents).
+
+check_erlcinfo(_Config, #erlcinfo{vsn=?ERLCINFO_VSN}) ->
+ ok;
+check_erlcinfo(Config, #erlcinfo{vsn=Vsn}) ->
+ ?ABORT("~s file version is incompatible. expected: ~b got: ~b~n",
+ [erlcinfo_file(Config), ?ERLCINFO_VSN, Vsn]);
+check_erlcinfo(Config, _) ->
+ ?ABORT("~s file is invalid. Please delete before next run.~n",
+ [erlcinfo_file(Config)]).
+
+erlcinfo_file(Config) ->
+ filename:join([rebar_utils:base_dir(Config), ".rebar", ?ERLCINFO_FILE]).
+
+init_erlcinfo(Config, Erls) ->
+ G = restore_erlcinfo(Config),
+ %% Get a unique list of dirs based on the source files' locations.
+ %% This is used for finding files in sub dirs of the configured
+ %% src_dirs. For example, src/sub_dir/foo.erl.
+ Dirs = sets:to_list(lists:foldl(
+ fun(Erl, Acc) ->
+ Dir = filename:dirname(Erl),
+ sets:add_element(Dir, Acc)
+ end, sets:new(), Erls)),
+ Updates = [update_erlcinfo(G, Erl, include_path(Erl, Config) ++ Dirs)
+ || Erl <- Erls],
+ Modified = lists:member(modified, Updates),
+ ok = store_erlcinfo(G, Config, Modified),
+ G.
+
+update_erlcinfo(G, Source, Dirs) ->
+ case digraph:vertex(G, Source) of
+ {_, LastUpdated} ->
+ LastModified = filelib:last_modified(Source),
+ if LastModified == 0 ->
+ %% The file doesn't exist anymore,
+ %% erase it from the graph.
+ %% All the edges will be erased automatically.
+ digraph:del_vertex(G, Source),
+ modified;
+ LastUpdated < LastModified ->
+ modify_erlcinfo(G, Source, Dirs);
+ modified;
+ true ->
+ unmodified
+ end;
+ false ->
+ modify_erlcinfo(G, Source, Dirs),
+ modified
+ end.
+
+modify_erlcinfo(G, Source, Dirs) ->
+ {ok, Fd} = file:open(Source, [read]),
+ Incls = parse_attrs(Fd, []),
+ AbsIncls = expand_file_names(Incls, Dirs),
+ ok = file:close(Fd),
+ LastUpdated = {date(), time()},
+ digraph:add_vertex(G, Source, LastUpdated),
+ lists:foreach(
+ fun(Incl) ->
+ update_erlcinfo(G, Incl, Dirs),
+ digraph:add_edge(G, Source, Incl)
+ end, AbsIncls).
+
+restore_erlcinfo(Config) ->
+ File = erlcinfo_file(Config),
+ G = digraph:new(),
+ case file:read_file(File) of
+ {ok, Data} ->
+ try binary_to_term(Data) of
+ Erlcinfo ->
+ ok = check_erlcinfo(Config, Erlcinfo),
+ #erlcinfo{info=ErlcInfo} = Erlcinfo,
+ {Vs, Es} = ErlcInfo,
+ lists:foreach(
+ fun({V, LastUpdated}) ->
+ digraph:add_vertex(G, V, LastUpdated)
+ end, Vs),
+ lists:foreach(
+ fun({V1, V2}) ->
+ digraph:add_edge(G, V1, V2)
+ end, Es)
+ catch
+ error:badarg ->
+ ?ERROR(
+ "Failed (binary_to_term) to restore rebar info file."
+ " Discard file.~n", []),
+ ok
+ end;
+ _Err ->
+ ok
+ end,
+ G.
+
+store_erlcinfo(_G, _Config, _Modified = false) ->
+ ok;
+store_erlcinfo(G, Config, _Modified) ->
+ Vs = lists:map(
+ fun(V) ->
+ digraph:vertex(G, V)
+ end, digraph:vertices(G)),
+ Es = lists:flatmap(
+ fun({V, _}) ->
+ lists:map(
+ fun(E) ->
+ {_, V1, V2, _} = digraph:edge(G, E),
+ {V1, V2}
+ end, digraph:out_edges(G, V))
+ end, Vs),
+ File = erlcinfo_file(Config),
+ ok = filelib:ensure_dir(File),
+ Data = term_to_binary(#erlcinfo{info={Vs, Es}}, [{compressed, 9}]),
+ file:write_file(File, Data).
+
+%% NOTE: If, for example, one of the entries in Files, refers to
+%% gen_server.erl, that entry will be dropped. It is dropped because
+%% such an entry usually refers to the beam file, and we don't pass a
+%% list of OTP src dirs for finding gen_server.erl's full path. Also,
+%% if gen_server.erl was modified, it's not rebar's task to compile a
+%% new version of the beam file. Therefore, it's reasonable to drop
+%% such entries. Also see process_attr(behaviour, Form, Includes).
+-spec expand_file_names([file:filename()],
+ [file:filename()]) -> [file:filename()].
+expand_file_names(Files, Dirs) ->
+ %% We check if Files exist by itself or within the directories
+ %% listed in Dirs.
+ %% Return the list of files matched.
+ lists:flatmap(
+ fun(Incl) ->
+ case filelib:is_regular(Incl) of
+ true ->
+ [Incl];
+ false ->
+ lists:flatmap(
+ fun(Dir) ->
+ FullPath = filename:join(Dir, Incl),
+ case filelib:is_regular(FullPath) of
+ true ->
+ [FullPath];
+ false ->
+ []
+ end
+ end, Dirs)
+ end
+ end, Files).
+
+-spec get_parents(digraph(), file:filename()) -> [file:filename()].
+get_parents(G, Source) ->
+ %% Return all files which the Source depends upon.
+ digraph_utils:reachable_neighbours([Source], G).
+
+-spec get_children(digraph(), file:filename()) -> [file:filename()].
+get_children(G, Source) ->
+ %% Return all files dependent on the Source.
+ digraph_utils:reaching_neighbours([Source], G).
-spec internal_erl_compile(rebar_config:config(), file:filename(),
- file:filename(), list()) -> 'ok' | 'skipped'.
-internal_erl_compile(Config, Source, Outdir, ErlOpts) ->
+ file:filename(), list(),
+ digraph()) -> 'ok' | 'skipped'.
+internal_erl_compile(Config, Source, OutDir, ErlOpts, G) ->
%% Determine the target name and includes list by inspecting the source file
- {Module, Hrls} = inspect(Source, include_path(Source, Config)),
+ Module = filename:basename(Source, ".erl"),
+ Parents = get_parents(G, Source),
+ log_files(?FMT("~s depends on", [Source]), Parents),
%% Construct the target filename
- Target = filename:join([Outdir | string:tokens(Module, ".")]) ++ ".beam",
+ Target = filename:join([OutDir | string:tokens(Module, ".")]) ++ ".beam",
ok = filelib:ensure_dir(Target),
%% If the file needs compilation, based on last mod date of includes or
%% the target
- case needs_compile(Source, Target, Hrls) of
+ case needs_compile(Source, Target, Parents) of
true ->
Opts = [{outdir, filename:dirname(Target)}] ++
ErlOpts ++ [{i, "include"}, return],
@@ -401,7 +572,14 @@ compile_mib(Source, Target, Config) ->
case snmpc:compile(Source, Opts) of
{ok, _} ->
Mib = filename:rootname(Target),
- ok = snmpc:mib_to_hrl(Mib),
+ MibToHrlOpts =
+ case proplists:get_value(verbosity, Opts, undefined) of
+ undefined ->
+ #options{specific = []};
+ Verbosity ->
+ #options{specific = [{verbosity, Verbosity}]}
+ end,
+ ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts),
Hrl_filename = Mib ++ ".hrl",
rebar_file_utils:mv(Hrl_filename, "include"),
ok;
@@ -455,40 +633,97 @@ delete_dir(Dir, Subdirs) ->
lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
file:del_dir(Dir).
--spec compile_priority(file:filename()) -> 'normal' | 'behaviour' |
- 'callback' |
- 'parse_transform'.
-compile_priority(File) ->
- case epp_dodger:parse_file(File) of
- {error, _} ->
- normal; % couldn't parse the file, default priority
- {ok, Trees} ->
- F2 = fun({tree,arity_qualifier,_,
- {arity_qualifier,{tree,atom,_,behaviour_info},
- {tree,integer,_,1}}}, _) ->
- behaviour;
- ({tree,arity_qualifier,_,
- {arity_qualifier,{tree,atom,_,parse_transform},
- {tree,integer,_,2}}}, _) ->
- parse_transform;
- (_, Acc) ->
- Acc
- end,
-
- F = fun({tree, attribute, _,
- {attribute, {tree, atom, _, export},
- [{tree, list, _, {list, List, none}}]}}, Acc) ->
- lists:foldl(F2, Acc, List);
- ({tree, attribute, _,
- {attribute, {tree, atom, _, callback},_}}, _Acc) ->
- callback;
- (_, Acc) ->
- Acc
- end,
+parse_attrs(Fd, Includes) ->
+ case io:parse_erl_form(Fd, "") of
+ {ok, Form, _Line} ->
+ case erl_syntax:type(Form) of
+ attribute ->
+ NewIncludes = process_attr(Form, Includes),
+ parse_attrs(Fd, NewIncludes);
+ _ ->
+ parse_attrs(Fd, Includes)
+ end;
+ {eof, _} ->
+ Includes;
+ _Err ->
+ parse_attrs(Fd, Includes)
+ end.
- lists:foldl(F, normal, Trees)
+process_attr(Form, Includes) ->
+ try
+ AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
+ process_attr(AttrName, Form, Includes)
+ catch _:_ ->
+ %% TODO: We should probably try to be more specific here
+ %% and not suppress all errors.
+ Includes
end.
+process_attr(import, Form, Includes) ->
+ case erl_syntax_lib:analyze_import_attribute(Form) of
+ {Mod, _Funs} ->
+ [atom_to_list(Mod) ++ ".erl"|Includes];
+ Mod ->
+ [atom_to_list(Mod) ++ ".erl"|Includes]
+ end;
+process_attr(file, Form, Includes) ->
+ {File, _} = erl_syntax_lib:analyze_file_attribute(Form),
+ [File|Includes];
+process_attr(include, Form, Includes) ->
+ [FileNode] = erl_syntax:attribute_arguments(Form),
+ File = erl_syntax:string_value(FileNode),
+ [File|Includes];
+process_attr(include_lib, Form, Includes) ->
+ [FileNode] = erl_syntax:attribute_arguments(Form),
+ RawFile = erl_syntax:string_value(FileNode),
+ File = maybe_expand_include_lib_path(RawFile),
+ [File|Includes];
+process_attr(behaviour, Form, Includes) ->
+ [FileNode] = erl_syntax:attribute_arguments(Form),
+ File = erl_syntax:atom_name(FileNode) ++ ".erl",
+ [File|Includes];
+process_attr(compile, Form, Includes) ->
+ [Arg] = erl_syntax:attribute_arguments(Form),
+ case erl_syntax:concrete(Arg) of
+ {parse_transform, Mod} ->
+ [atom_to_list(Mod) ++ ".erl"|Includes];
+ {core_transform, Mod} ->
+ [atom_to_list(Mod) ++ ".erl"|Includes];
+ L when is_list(L) ->
+ lists:foldl(
+ fun({parse_transform, M}, Acc) ->
+ [atom_to_list(M) ++ ".erl"|Acc];
+ ({core_transform, M}, Acc) ->
+ [atom_to_list(M) ++ ".erl"|Acc];
+ (_, Acc) ->
+ Acc
+ end, Includes, L)
+ end.
+
+%% Given the filename from an include_lib attribute, if the path
+%% exists, return unmodified, or else get the absolute ERL_LIBS
+%% path.
+maybe_expand_include_lib_path(File) ->
+ case filelib:is_regular(File) of
+ true ->
+ File;
+ false ->
+ expand_include_lib_path(File)
+ end.
+
+%% Given a path like "stdlib/include/erl_compile.hrl", return
+%% "OTP_INSTALL_DIR/lib/erlang/lib/stdlib-x.y.z/include/erl_compile.hrl".
+%% Usually a simple [Lib, SubDir, File1] = filename:split(File) should
+%% work, but to not crash when an unusual include_lib path is used,
+%% utilize more elaborate logic.
+expand_include_lib_path(File) ->
+ File1 = filename:basename(File),
+ Split = filename:split(filename:dirname(File)),
+ Lib = hd(Split),
+ SubDir = filename:join(tl(Split)),
+ Dir = code:lib_dir(list_to_atom(Lib), list_to_atom(SubDir)),
+ filename:join(Dir, File1).
+
%%
%% Ensure all files in a list are present and abort if one is missing
%%
@@ -501,3 +736,13 @@ check_file(File) ->
false -> ?ABORT("File ~p is missing, aborting\n", [File]);
true -> File
end.
+
+%% Print prefix followed by list of files. If the list is empty, print
+%% on the same line, otherwise use a separate line.
+log_files(Prefix, Files) ->
+ case Files of
+ [] ->
+ ?DEBUG("~s: ~p~n", [Prefix, Files]);
+ _ ->
+ ?DEBUG("~s:~n~p~n", [Prefix, Files])
+ end.
diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl
index 4449be6..556e841 100644
--- a/src/rebar_erlydtl_compiler.erl
+++ b/src/rebar_erlydtl_compiler.erl
@@ -116,8 +116,8 @@ compile(Config, _AppFile) ->
option(source_ext, DtlOpts),
option(out_dir, DtlOpts),
option(module_ext, DtlOpts) ++ ".beam",
- fun(S, T, _C) ->
- compile_dtl(S, T, DtlOpts)
+ fun(S, T, C) ->
+ compile_dtl(C, S, T, DtlOpts)
end,
[{check_last_mod, false},
{recursive, option(recursive, DtlOpts)}])
@@ -169,58 +169,71 @@ default(out_dir) -> "ebin";
default(source_ext) -> ".dtl";
default(module_ext) -> "_dtl";
default(custom_tags_dir) -> "";
-default(compiler_options) -> [report, return];
+default(compiler_options) -> [return];
default(recursive) -> true.
-compile_dtl(Source, Target, DtlOpts) ->
+compile_dtl(Config, Source, Target, DtlOpts) ->
case code:which(erlydtl) of
non_existing ->
?ERROR("~n===============================================~n"
" You need to install erlydtl to compile DTL templates~n"
" Download the latest tarball release from github~n"
- " http://code.google.com/p/erlydtl/~n"
+ " https://github.com/erlydtl/erlydtl/releases~n"
" and install it into your erlang library dir~n"
"===============================================~n~n", []),
?FAIL;
_ ->
case needs_compile(Source, Target, DtlOpts) of
true ->
- do_compile(Source, Target, DtlOpts);
+ do_compile(Config, Source, Target, DtlOpts);
false ->
skipped
end
end.
-do_compile(Source, Target, DtlOpts) ->
+do_compile(Config, Source, Target, DtlOpts) ->
%% TODO: Check last mod on target and referenced DTLs here..
+ %% erlydtl >= 0.8.1 does not use the extra indirection using the
+ %% compiler_options. Kept for backward compatibility with older
+ %% versions of erlydtl.
+ CompilerOptions = option(compiler_options, DtlOpts),
+
+ Sorted = proplists:unfold(
+ lists:sort(
+ [{out_dir, option(out_dir, DtlOpts)},
+ {doc_root, option(doc_root, DtlOpts)},
+ {custom_tags_dir, option(custom_tags_dir, DtlOpts)},
+ {compiler_options, CompilerOptions}
+ |CompilerOptions])),
+
%% ensure that doc_root and out_dir are defined,
%% using defaults if necessary
- Opts = lists:ukeymerge(1,
- DtlOpts,
- lists:sort(
- [{out_dir, option(out_dir, DtlOpts)},
- {doc_root, option(doc_root, DtlOpts)},
- {custom_tags_dir, option(custom_tags_dir, DtlOpts)},
- {compiler_options, option(compiler_options, DtlOpts)}])),
+ Opts = lists:ukeymerge(1, DtlOpts, Sorted),
?INFO("Compiling \"~s\" -> \"~s\" with options:~n ~s~n",
[Source, Target, io_lib:format("~p", [Opts])]),
case erlydtl:compile(Source,
module_name(Target),
Opts) of
- ok -> ok;
- {error, {File, [{Pos, _Mod, Err}]}} ->
- ?ERROR("Compiling template ~p failed:~n (~s): ~p~n",
- [File, err_location(Pos), Err]);
- Reason ->
- ?ERROR("Compiling template ~s failed:~n ~p~n",
- [Source, Reason]),
- ?FAIL
+ ok ->
+ ok;
+ {ok, _Mod} ->
+ ok;
+ {ok, _Mod, Ws} ->
+ rebar_base_compiler:ok_tuple(Config, Source, Ws);
+ {ok, _Mod, _Bin, Ws} ->
+ rebar_base_compiler:ok_tuple(Config, Source, Ws);
+ error ->
+ rebar_base_compiler:error_tuple(Config, Source, [], [], Opts);
+ {error, {_File, _Msgs} = Error} ->
+ rebar_base_compiler:error_tuple(Config, Source, [Error], [], Opts);
+ {error, Msg} ->
+ Es = [{Source, [{erlydtl_parser, Msg}]}],
+ rebar_base_compiler:error_tuple(Config, Source, Es, [], Opts);
+ {error, Es, Ws} ->
+ rebar_base_compiler:error_tuple(Config, Source, Es, Ws, Opts)
end.
-err_location({L,C}) -> io_lib:format("line:~w, col:~w", [L, C]);
-err_location(L) -> io_lib:format("line:~w", [L]).
-
module_name(Target) ->
F = filename:basename(Target),
string:substr(F, 1, length(F)-length(".beam")).
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index cdc0787..d969f96 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -84,8 +84,7 @@ eunit(Config, _AppFile) ->
ok = ensure_dirs(),
%% Save code path
CodePath = setup_code_path(),
- CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
- false),
+ CompileOnly = rebar_config:get_global(Config, compile_only, false),
{ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "eunit",
?EUNIT_DIR),
case CompileOnly of
@@ -121,12 +120,16 @@ info_help(Description) ->
" ~p~n"
" ~p~n"
"Valid command line options:~n"
- " suites=\"foo,bar\" (Run tests in foo.erl, test/foo_tests.erl and~n"
+ " suite[s]=\"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"
+ " test[s]=\"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",
+ " suite's _tests module)~n"
+ " random_suite_order=true (Run tests in random order)~n"
+ " random_suite_order=Seed (Run tests in random order,~n"
+ " with the PRNG seeded with Seed)~n"
+ " compile_only=true (Compile but do not run tests)",
[
Description,
{eunit_opts, []},
@@ -150,7 +153,7 @@ run_eunit(Config, CodePath, SrcErls) ->
AllBeamFiles),
OtherBeamFiles = TestBeamFiles --
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
- ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
+ ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles),
%% Get modules to be run in eunit
AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
@@ -215,7 +218,7 @@ setup_code_path() ->
%%
filter_suites(Config, Modules) ->
- RawSuites = rebar_config:get_global(Config, suites, ""),
+ RawSuites = get_suites(Config),
SuitesProvided = RawSuites =/= "",
Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
{SuitesProvided, filter_suites1(Modules, Suites)}.
@@ -223,7 +226,42 @@ 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_suites(Config) ->
+ case rebar_config:get_global(Config, suites, "") of
+ "" ->
+ rebar_config:get_global(Config, suite, "");
+ Suites ->
+ Suites
+ end.
+
+%%
+%% == randomize suites ==
+%%
+
+randomize_suites(Config, Modules) ->
+ case rebar_config:get_global(Config, random_suite_order, undefined) of
+ undefined ->
+ Modules;
+ "true" ->
+ Seed = crypto:rand_uniform(1, 65535),
+ randomize_suites1(Modules, Seed);
+ String ->
+ try list_to_integer(String) of
+ Seed ->
+ randomize_suites1(Modules, Seed)
+ catch
+ error:badarg ->
+ ?ERROR("Bad random seed provided: ~p~n", [String]),
+ ?FAIL
+ end
+ end.
+
+randomize_suites1(Modules, Seed) ->
+ _ = random:seed(35, Seed, 1337),
+ ?CONSOLE("Randomizing suite order with seed ~b~n", [Seed]),
+ [X||{_,X} <- lists:sort([{random:uniform(), M} || M <- Modules])].
%%
%% == get matching tests ==
@@ -259,8 +297,16 @@ get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
end,
get_matching_tests(Config, Modules).
+get_tests(Config) ->
+ case rebar_config:get_global(Config, tests, "") of
+ "" ->
+ rebar_config:get_global(Config, test, "");
+ Suites ->
+ Suites
+ end.
+
get_matching_tests(Config, Modules) ->
- RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
+ RawFunctions = get_tests(Config),
Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
case Tests of
[] ->
@@ -408,7 +454,7 @@ perform_eunit(Config, Tests) ->
get_eunit_opts(Config) ->
%% Enable verbose in eunit if so requested..
- BaseOpts = case rebar_config:is_verbose(Config) of
+ BaseOpts = case rebar_log:is_verbose(Config) of
true ->
[verbose];
false ->
@@ -561,7 +607,10 @@ align_notcovered_count(Module, Covered, NotCovered, true) ->
cover_write_index(Coverage, SrcModules) ->
{ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]),
- ok = file:write(F, "<html><head><title>Coverage Summary</title></head>\n"),
+ ok = file:write(F, "<!DOCTYPE HTML><html>\n"
+ "<head><meta charset=\"utf-8\">"
+ "<title>Coverage Summary</title></head>\n"
+ "<body>\n"),
IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end,
{SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage),
cover_write_index_section(F, "Source", SrcCoverage),
@@ -579,7 +628,7 @@ cover_write_index_section(F, SectionName, Coverage) ->
TotalCoverage = percentage(Covered, NotCovered),
%% Write the report
- ok = file:write(F, ?FMT("<body><h1>~s Summary</h1>\n", [SectionName])),
+ ok = file:write(F, ?FMT("<h1>~s Summary</h1>\n", [SectionName])),
ok = file:write(F, ?FMT("<h3>Total: ~s</h3>\n", [TotalCoverage])),
ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"),
@@ -666,7 +715,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),
@@ -798,11 +848,11 @@ pause_until_net_kernel_stopped() ->
pause_until_net_kernel_stopped(0) ->
exit(net_kernel_stop_failed);
pause_until_net_kernel_stopped(N) ->
- try
- timer:sleep(100),
- pause_until_net_kernel_stopped(N - 1)
- catch
- error:badarg ->
+ case node() of
+ 'nonode@nohost' ->
?DEBUG("Stopped net kernel.\n", []),
- ok
+ ok;
+ _ ->
+ timer:sleep(100),
+ pause_until_net_kernel_stopped(N - 1)
end.
diff --git a/src/rebar_getopt.erl b/src/rebar_getopt.erl
new file mode 100644
index 0000000..79b871d
--- /dev/null
+++ b/src/rebar_getopt.erl
@@ -0,0 +1,914 @@
+%%%-------------------------------------------------------------------
+%%% @author Juan Jose Comellas <juanjo@comellas.org>
+%%% @copyright (C) 2009 Juan Jose Comellas
+%%% @doc Parses command line options with a format similar to that of GNU getopt.
+%%% @end
+%%%
+%%% This source file is subject to the New BSD License. You should have received
+%%% a copy of the New BSD license with this software. If not, it can be
+%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php
+%%%-------------------------------------------------------------------
+-module(rebar_getopt).
+-author('juanjo@comellas.org').
+
+-export([parse/2, check/2, parse_and_check/2, format_error/2,
+ usage/2, usage/3, usage/4, tokenize/1]).
+-export([usage_cmd_line/2]).
+
+-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).
+-define(OPT_SHORT, 2).
+-define(OPT_LONG, 3).
+-define(OPT_ARG, 4).
+-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'.
+%% Data type that an argument can be converted to.
+-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
+%% Argument specification.
+-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().
+%% Command line option specification.
+-type option_spec() :: {
+ Name :: atom(),
+ Short :: char() | undefined,
+ Long :: string() | undefined,
+ ArgSpec :: arg_spec(),
+ Help :: string() | undefined
+ }.
+%% Output streams
+-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. Additionally perform check if all required options (the ones
+%% without default values) are present. The function is a combination of
+%% two calls: parse/2 and check/2.
+-spec parse_and_check([option_spec()], string() | [string()]) ->
+ {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: term()}}.
+parse_and_check(OptSpecList, CmdLine) when is_list(OptSpecList), is_list(CmdLine) ->
+ case parse(OptSpecList, CmdLine) of
+ {ok, {Opts, _}} = Result ->
+ case check(OptSpecList, Opts) of
+ ok -> Result;
+ Error -> Error
+ end;
+ Error ->
+ Error
+ end.
+
+%% @doc Check the parsed command line arguments returning ok if all required
+%% options (i.e. that don't have defaults) are present, and returning
+%% error otherwise.
+-spec check([option_spec()], [option()]) ->
+ ok | {error, {Reason :: atom(), Option :: atom()}}.
+check(OptSpecList, ParsedOpts) when is_list(OptSpecList), is_list(ParsedOpts) ->
+ try
+ RequiredOpts = [Name || {Name, _, _, Arg, _} <- OptSpecList,
+ not is_tuple(Arg) andalso Arg =/= undefined],
+ lists:foreach(fun (Option) ->
+ case proplists:is_defined(Option, ParsedOpts) of
+ true ->
+ ok;
+ false ->
+ throw({error, {missing_required_option, Option}})
+ end
+ end, RequiredOpts)
+ catch
+ _:Error ->
+ Error
+ end.
+
+
+%% @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 :: term()}}.
+parse(OptSpecList, CmdLine) when is_list(CmdLine) ->
+ try
+ Args = if
+ is_integer(hd(CmdLine)) -> tokenize(CmdLine);
+ true -> CmdLine
+ end,
+ parse(OptSpecList, [], [], 0, Args)
+ catch
+ throw: {error, {_Reason, _Data}} = Error ->
+ Error
+ end.
+
+
+-spec parse([option_spec()], [option()], [string()], integer(), [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.
+ {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}};
+%% Process long options.
+parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) ->
+ parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg);
+%% Process short options.
+parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) ->
+ parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg);
+%% Process non-option arguments.
+parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) ->
+ case find_non_option_arg(OptSpecList, ArgPos) of
+ {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) ->
+ parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos + 1, Tail);
+ false ->
+ parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail)
+ end;
+parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
+ %% Once we have completed gathering the options we add the ones that were
+ %% not present but had default arguments in the specification.
+ {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}.
+
+
+%% @doc Format the error code returned by prior call to parse/2 or check/2.
+-spec format_error([option_spec()], {error, {Reason :: atom(), Data :: term()}} |
+ {Reason :: term(), Data :: term()}) -> string().
+format_error(OptSpecList, {error, Reason}) ->
+ format_error(OptSpecList, Reason);
+format_error(OptSpecList, {missing_required_option, Name}) ->
+ {_Name, Short, Long, _Type, _Help} = lists:keyfind(Name, 1, OptSpecList),
+ lists:flatten(["missing required option: -", [Short], " (", to_string(Long), ")"]);
+format_error(_OptSpecList, {invalid_option, OptStr}) ->
+ lists:flatten(["invalid option: ", to_string(OptStr)]);
+format_error(_OptSpecList, {invalid_option_arg, {Name, Arg}}) ->
+ lists:flatten(["option \'", to_string(Name) ++ "\' has invalid argument: ", to_string(Arg)]);
+format_error(_OptSpecList, {invalid_option_arg, OptStr}) ->
+ lists:flatten(["invalid option argument: ", to_string(OptStr)]);
+format_error(_OptSpecList, {Reason, Data}) ->
+ lists:flatten([to_string(Reason), " ", to_string(Data)]).
+
+
+%% @doc Parse a long option, add it to the option accumulator and continue
+%% parsing the rest of the arguments recursively.
+%% A long option can have the following syntax:
+%% --foo Single option 'foo', no argument
+%% --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()]}}.
+parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
+ case split_assigned_arg(OptArg) of
+ {Long, Arg} ->
+ %% Get option that has its argument within the same string
+ %% separated by an equal ('=') character (e.g. "--port=1000").
+ parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg);
+
+ Long ->
+ case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of
+ {Name, _Short, Long, undefined, _Help} ->
+ parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args);
+
+ {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec ->
+ %% The option argument string is empty, but the option requires
+ %% an argument, so we look into the next string in the list.
+ %% e.g ["--port", "1000"]
+ parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec);
+ false ->
+ throw({error, {invalid_option, OptStr}})
+ end
+ end.
+
+
+%% @doc Parse an option where the argument is 'assigned' in the same string using
+%% the '=' character, add it to the option accumulator and continue parsing the
+%% rest of the arguments recursively. This syntax is only valid for long options.
+-spec parse_long_option_assigned_arg([option_spec()], [option()], [string()], integer(),
+ [string()], string(), string(), string()) ->
+ {ok, {[option()], [string()]}}.
+parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) ->
+ case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of
+ {_Name, _Short, Long, ArgSpec, _Help} = OptSpec ->
+ case ArgSpec of
+ undefined ->
+ throw({error, {invalid_option_arg, OptStr}});
+ _ ->
+ parse(OptSpecList, add_option_with_assigned_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args)
+ end;
+ false ->
+ throw({error, {invalid_option, OptStr}})
+ end.
+
+
+%% @doc Split an option string that may contain an option with its argument
+%% separated by an equal ('=') character (e.g. "port=1000").
+-spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string().
+split_assigned_arg(OptStr) ->
+ split_assigned_arg(OptStr, OptStr, []).
+
+split_assigned_arg(_OptStr, "=" ++ Tail, Acc) ->
+ {lists:reverse(Acc), Tail};
+split_assigned_arg(OptStr, [Char | Tail], Acc) ->
+ split_assigned_arg(OptStr, Tail, [Char | Acc]);
+split_assigned_arg(OptStr, [], _Acc) ->
+ OptStr.
+
+
+%% @doc Retrieve the argument for an option from the next string in the list of
+%% command-line parameters or set the value of the argument from the argument
+%% specification (for boolean and integer arguments), if possible.
+parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) ->
+ ArgSpecType = arg_spec_type(ArgSpec),
+ case Args =:= [] orelse is_implicit_arg(ArgSpecType, hd(Args)) of
+ true ->
+ parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
+ false ->
+ [Arg | Tail] = Args,
+ try
+ parse(OptSpecList, [{Name, to_type(ArgSpecType, Arg)} | OptAcc], ArgAcc, ArgPos, Tail)
+ catch
+ error:_ ->
+ throw({error, {invalid_option_arg, {Name, Arg}}})
+ end
+ end.
+
+
+%% @doc Parse a short option, add it to the option accumulator and continue
+%% parsing the rest of the arguments recursively.
+%% A short option can have the following syntax:
+%% -a Single option 'a', no argument or implicit boolean argument
+%% -a foo Single option 'a', argument "foo"
+%% -afoo Single option 'a', argument "foo"
+%% -abc Multiple options: 'a'; 'b'; 'c'
+%% -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()]}}.
+parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
+ parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg).
+
+parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptPos, [Short | Arg]) ->
+ case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of
+ {Name, Short, _Long, undefined, _Help} ->
+ parse_short_option(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, first, Arg);
+
+ {_Name, Short, _Long, ArgSpec, _Help} = OptSpec ->
+ %% The option has a specification, so it requires an argument.
+ case Arg of
+ [] ->
+ %% The option argument string is empty, but the option requires
+ %% an argument, so we look into the next string in the list.
+ parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec, OptPos);
+
+ _ ->
+ case is_valid_arg(ArgSpec, Arg) of
+ true ->
+ parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args);
+ _ ->
+ NewOptAcc = case OptPos of
+ first -> add_option_with_implicit_arg(OptSpec, OptAcc);
+ _ -> add_option_with_implicit_incrementable_arg(OptSpec, OptAcc)
+ end,
+ parse_short_option(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Args, OptStr, next, Arg)
+ end
+ end;
+
+ false ->
+ throw({error, {invalid_option, OptStr}})
+ end;
+parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, _OptPos, []) ->
+ parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args).
+
+
+%% @doc Retrieve the argument for an option from the next string in the list of
+%% command-line parameters or set the value of the argument from the argument
+%% specification (for boolean and integer arguments), if possible.
+parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptPos) ->
+ case Args =:= [] orelse is_implicit_arg(ArgSpec, hd(Args)) of
+ true when OptPos =:= first ->
+ parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
+ true ->
+ parse(OptSpecList, add_option_with_implicit_incrementable_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args);
+ false ->
+ [Arg | Tail] = Args,
+ try
+ parse(OptSpecList, [{Name, to_type(ArgSpec, Arg)} | OptAcc], ArgAcc, ArgPos, Tail)
+ catch
+ error:_ ->
+ throw({error, {invalid_option_arg, {Name, Arg}}})
+ end
+ end.
+
+
+%% @doc Find the option for the discrete argument in position specified in the
+%% 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};
+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) ->
+ find_non_option_arg(Tail, Pos);
+find_non_option_arg([], _Pos) ->
+ false.
+
+
+%% @doc Append options that were not present in the command line arguments with
+%% their default arguments.
+-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);
+%% For options with no default argument.
+append_default_options([_Head | Tail], OptAcc) ->
+ append_default_options(Tail, OptAcc);
+append_default_options([], OptAcc) ->
+ OptAcc.
+
+
+%% @doc Add an option with argument converting it to the data type indicated by the
+%% argument specification.
+-spec add_option_with_arg(option_spec(), string(), [option()]) -> [option()].
+add_option_with_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) ->
+ case is_valid_arg(ArgSpec, Arg) of
+ true ->
+ try
+ [{Name, to_type(ArgSpec, Arg)} | OptAcc]
+ catch
+ error:_ ->
+ throw({error, {invalid_option_arg, {Name, Arg}}})
+ end;
+ false ->
+ add_option_with_implicit_arg(OptSpec, OptAcc)
+ end.
+
+
+%% @doc Add an option with argument that was part of an assignment expression
+%% (e.g. "--verbose=3") converting it to the data type indicated by the
+%% argument specification.
+-spec add_option_with_assigned_arg(option_spec(), string(), [option()]) -> [option()].
+add_option_with_assigned_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg, OptAcc) ->
+ try
+ [{Name, to_type(ArgSpec, Arg)} | OptAcc]
+ catch
+ error:_ ->
+ throw({error, {invalid_option_arg, {Name, Arg}}})
+ end.
+
+
+%% @doc Add an option that required an argument but did not have one. Some data
+%% types (boolean, integer) allow implicit or assumed arguments.
+-spec add_option_with_implicit_arg(option_spec(), [option()]) -> [option()].
+add_option_with_implicit_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) ->
+ case arg_spec_type(ArgSpec) of
+ boolean ->
+ %% Special case for boolean arguments: if there is no argument we
+ %% set the value to 'true'.
+ [{Name, true} | OptAcc];
+ integer ->
+ %% Special case for integer arguments: if the option had not been set
+ %% before we set the value to 1. This is needed to support options like
+ %% "-v" to return something like {verbose, 1}.
+ [{Name, 1} | OptAcc];
+ _ ->
+ throw({error, {missing_option_arg, Name}})
+ end.
+
+
+%% @doc Add an option with an implicit or assumed argument.
+-spec add_option_with_implicit_incrementable_arg(option_spec() | arg_spec(), [option()]) -> [option()].
+add_option_with_implicit_incrementable_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) ->
+ case arg_spec_type(ArgSpec) of
+ boolean ->
+ %% Special case for boolean arguments: if there is no argument we
+ %% set the value to 'true'.
+ [{Name, true} | OptAcc];
+ integer ->
+ %% Special case for integer arguments: if the option had not been set
+ %% before we set the value to 1; if not we increment the previous value
+ %% the option had. This is needed to support options like "-vvv" to
+ %% return something like {verbose, 3}.
+ case OptAcc of
+ [{Name, Count} | Tail] ->
+ [{Name, Count + 1} | Tail];
+ _ ->
+ [{Name, 1} | OptAcc]
+ end;
+ _ ->
+ throw({error, {missing_option_arg, Name}})
+ end.
+
+
+%% @doc Retrieve the data type form an argument specification.
+-spec arg_spec_type(arg_spec()) -> arg_type() | undefined.
+arg_spec_type({Type, _DefaultArg}) ->
+ Type;
+arg_spec_type(Type) when is_atom(Type) ->
+ Type.
+
+
+%% @doc Convert an argument string to its corresponding data type.
+-spec to_type(arg_spec() | arg_type(), string()) -> arg_value().
+to_type({Type, _DefaultArg}, Arg) ->
+ to_type(Type, Arg);
+to_type(binary, Arg) ->
+ list_to_binary(Arg);
+to_type(atom, Arg) ->
+ list_to_atom(Arg);
+to_type(integer, Arg) ->
+ list_to_integer(Arg);
+to_type(float, Arg) ->
+ list_to_float(Arg);
+to_type(boolean, Arg) ->
+ LowerArg = string:to_lower(Arg),
+ case is_arg_true(LowerArg) of
+ true ->
+ true;
+ _ ->
+ case is_arg_false(LowerArg) of
+ true ->
+ false;
+ false ->
+ erlang:error(badarg)
+ end
+ end;
+to_type(_Type, Arg) ->
+ Arg.
+
+
+-spec is_arg_true(string()) -> boolean().
+is_arg_true(Arg) ->
+ (Arg =:= "true") orelse (Arg =:= "t") orelse
+ (Arg =:= "yes") orelse (Arg =:= "y") orelse
+ (Arg =:= "on") orelse (Arg =:= "enabled") orelse
+ (Arg =:= "1").
+
+
+-spec is_arg_false(string()) -> boolean().
+is_arg_false(Arg) ->
+ (Arg =:= "false") orelse (Arg =:= "f") orelse
+ (Arg =:= "no") orelse (Arg =:= "n") orelse
+ (Arg =:= "off") orelse (Arg =:= "disabled") orelse
+ (Arg =:= "0").
+
+
+-spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean().
+is_valid_arg({Type, _DefaultArg}, Arg) ->
+ is_valid_arg(Type, Arg);
+is_valid_arg(boolean, Arg) ->
+ is_boolean_arg(Arg);
+is_valid_arg(integer, Arg) ->
+ is_non_neg_integer_arg(Arg);
+is_valid_arg(float, Arg) ->
+ is_non_neg_float_arg(Arg);
+is_valid_arg(_Type, _Arg) ->
+ true.
+
+
+-spec is_implicit_arg(arg_spec(), nonempty_string()) -> boolean().
+is_implicit_arg({Type, _DefaultArg}, Arg) ->
+ is_implicit_arg(Type, Arg);
+is_implicit_arg(boolean, Arg) ->
+ not is_boolean_arg(Arg);
+is_implicit_arg(integer, Arg) ->
+ not is_integer_arg(Arg);
+is_implicit_arg(_Type, _Arg) ->
+ false.
+
+
+-spec is_boolean_arg(string()) -> boolean().
+is_boolean_arg(Arg) ->
+ LowerArg = string:to_lower(Arg),
+ is_arg_true(LowerArg) orelse is_arg_false(LowerArg).
+
+
+-spec is_integer_arg(string()) -> boolean().
+is_integer_arg("-" ++ Tail) ->
+ is_non_neg_integer_arg(Tail);
+is_integer_arg(Arg) ->
+ is_non_neg_integer_arg(Arg).
+
+
+-spec is_non_neg_integer_arg(string()) -> boolean().
+is_non_neg_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 ->
+ is_non_neg_integer_arg(Tail);
+is_non_neg_integer_arg([_Head | _Tail]) ->
+ false;
+is_non_neg_integer_arg([]) ->
+ true.
+
+
+-spec is_non_neg_float_arg(string()) -> boolean().
+is_non_neg_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. ->
+ is_non_neg_float_arg(Tail);
+is_non_neg_float_arg([_Head | _Tail]) ->
+ false;
+is_non_neg_float_arg([]) ->
+ true.
+
+
+%% @doc Show a message on standard_error indicating the command line options and
+%% arguments that are supported by the program.
+-spec usage([option_spec()], string()) -> ok.
+usage(OptSpecList, ProgramName) ->
+ 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, "~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).
+
+
+%% @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()], ProgramName :: string(), CmdLineTail :: string(), output_stream() | [{string(), string()}]) -> ok.
+usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) ->
+ 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).
+
+
+%% @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()], ProgramName :: string(), CmdLineTail :: string(),
+ [{OptionName :: string(), Help :: string()}], output_stream()) -> ok.
+usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, OutputStream) ->
+ 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;
+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 Length 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) ->
+ lists:flatten(io_lib:format("~w", [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.
+
+
+-spec line_length() -> 0..?LINE_LENGTH.
+line_length() ->
+ case io:columns() of
+ {ok, Columns} when Columns < ?LINE_LENGTH ->
+ Columns - 1;
+ _ ->
+ ?LINE_LENGTH
+ end.
+
+
+-spec to_string(term()) -> string().
+to_string(List) when is_list(List) ->
+ case io_lib:printable_list(List) of
+ true -> List;
+ false -> io_lib:format("~p", [List])
+ end;
+to_string(Atom) when is_atom(Atom) ->
+ atom_to_list(Atom);
+to_string(Value) ->
+ io_lib:format("~p", [Value]).
diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl
index 2a047d8..8488b0f 100644
--- a/src/rebar_lfe_compiler.erl
+++ b/src/rebar_lfe_compiler.erl
@@ -70,8 +70,8 @@ compile_lfe(Source, _Target, Config) ->
"~n", []),
?FAIL;
_ ->
- Opts = [{i, "include"}, {outdir, "ebin"}, return]
- ++ rebar_config:get_list(Config, erl_opts, []),
+ ErlOpts = rebar_utils:erl_opts(Config),
+ Opts = [{i, "include"}, {outdir, "ebin"}, return] ++ ErlOpts,
case lfe_comp:file(Source, Opts) of
{ok, _Mod, Ws} ->
rebar_base_compiler:ok_tuple(Config, Source, Ws);
diff --git a/src/rebar_log.erl b/src/rebar_log.erl
index 4108c9c..ba25332 100644
--- a/src/rebar_log.erl
+++ b/src/rebar_log.erl
@@ -27,8 +27,17 @@
-module(rebar_log).
-export([init/1,
- set_level/1, default_level/0,
- log/3]).
+ set_level/1,
+ error_level/0,
+ default_level/0,
+ log/3,
+ log/4,
+ is_verbose/1]).
+
+-define(ERROR_LEVEL, 0).
+-define(WARN_LEVEL, 1).
+-define(INFO_LEVEL, 2).
+-define(DEBUG_LEVEL, 3).
%% ===================================================================
%% Public API
@@ -37,35 +46,39 @@
init(Config) ->
Verbosity = rebar_config:get_global(Config, verbose, default_level()),
case valid_level(Verbosity) of
- 0 -> set_level(error);
- 1 -> set_level(warn);
- 2 -> set_level(info);
- 3 -> set_level(debug)
+ ?ERROR_LEVEL -> set_level(error);
+ ?WARN_LEVEL -> set_level(warn);
+ ?INFO_LEVEL -> set_level(info);
+ ?DEBUG_LEVEL -> set_level(debug)
end.
set_level(Level) ->
ok = application:set_env(rebar, log_level, Level).
log(Level, Str, Args) ->
+ log(standard_io, Level, Str, Args).
+
+log(Device, Level, Str, Args) ->
{ok, LogLevel} = application:get_env(rebar, log_level),
case should_log(LogLevel, Level) of
true ->
- io:format(log_prefix(Level) ++ Str, Args);
+ io:format(Device, log_prefix(Level) ++ Str, Args);
false ->
ok
end.
-default_level() -> error_level().
+error_level() -> ?ERROR_LEVEL.
+default_level() -> ?WARN_LEVEL.
+
+is_verbose(Config) ->
+ rebar_config:get_xconf(Config, is_verbose, false).
%% ===================================================================
%% Internal functions
%% ===================================================================
valid_level(Level) ->
- erlang:max(error_level(), erlang:min(Level, debug_level())).
-
-error_level() -> 0.
-debug_level() -> 3.
+ erlang:max(?ERROR_LEVEL, erlang:min(Level, ?DEBUG_LEVEL)).
should_log(debug, _) -> true;
should_log(info, debug) -> false;
diff --git a/src/mustache.erl b/src/rebar_mustache.erl
index f6963cd..9016c0f 100644
--- a/src/mustache.erl
+++ b/src/rebar_mustache.erl
@@ -23,7 +23,7 @@
%% See the README at http://github.com/mojombo/mustache.erl for additional
%% documentation and usage examples.
--module(mustache). %% v0.1.0
+-module(rebar_mustache). %% v0.1.0
-author("Tom Preston-Werner").
-export([compile/1, compile/2, render/1, render/2, render/3, get/2, get/3, escape/1, start/1]).
@@ -31,6 +31,8 @@
section_re = undefined,
tag_re = undefined}).
+-define(MUSTACHE_STR, "rebar_mustache").
+
compile(Body) when is_list(Body) ->
State = #mstate{},
CompiledTemplate = pre_compile(Body, State),
@@ -108,7 +110,7 @@ compile_section(Name, Content, State) ->
Mod = State#mstate.mod,
Result = compiler(Content, State),
"fun() -> " ++
- "case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
+ "case " ++ ?MUSTACHE_STR ++ ":get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
"\"true\" -> " ++
Result ++ "; " ++
"\"false\" -> " ++
@@ -143,10 +145,10 @@ tag_kind(T, {K0, K1}) ->
compile_tag(none, Content, State) ->
Mod = State#mstate.mod,
- "mustache:escape(mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))";
+ ?MUSTACHE_STR ++ ":escape(" ++ ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))";
compile_tag("{", Content, State) ->
Mod = State#mstate.mod,
- "mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")";
+ ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")";
compile_tag("!", _Content, _State) ->
"[]".
diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl
index 5607aa4..5549dc4 100644
--- a/src/rebar_neotoma_compiler.erl
+++ b/src/rebar_neotoma_compiler.erl
@@ -56,8 +56,8 @@ 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
@@ -70,10 +70,10 @@ info(help, compile) ->
"Valid rebar.config options:~n"
" ~p~n",
[
- {neotom_opts, [{doc_root, "src"},
- {out_dir, "src"},
- {source_ext, ".peg"},
- {module_ext, ""}]}
+ {neotoma_opts, [{doc_root, "src"},
+ {out_dir, "src"},
+ {source_ext, ".peg"},
+ {module_ext, ""}]}
]).
neotoma_opts(Config) ->
diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl
index 6c52336..b3566c8 100644
--- a/src/rebar_otp_app.erl
+++ b/src/rebar_otp_app.erl
@@ -119,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),
@@ -205,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_qc.erl b/src/rebar_qc.erl
index 53a6f52..99d37a2 100644
--- a/src/rebar_qc.erl
+++ b/src/rebar_qc.erl
@@ -67,7 +67,9 @@ info(help, qc) ->
"Valid rebar.config options:~n"
" {qc_opts, [{qc_mod, module()}, Options]}~n"
" ~p~n"
- " ~p~n",
+ " ~p~n"
+ "Valid command line options:~n"
+ " compile_only=true (Compile but do not test properties)",
[
{qc_compile_opts, []},
{qc_first_files, []}
@@ -142,8 +144,7 @@ run(Config, QC, QCOpts) ->
ok = ensure_dirs(),
CodePath = setup_codepath(),
- CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
- false),
+ CompileOnly = rebar_config:get_global(Config, compile_only, false),
%% Compile erlang code to ?QC_DIR, using a tweaked config
%% with appropriate defines, and include all the test modules
%% as well.
diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl
index 18b0702..9f9488e 100644
--- a/src/rebar_reltool.erl
+++ b/src/rebar_reltool.erl
@@ -166,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([]) ->
dict:new();
-load_vars_file(File) ->
+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_templater.erl b/src/rebar_templater.erl
index e997975..43bb8da 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -27,6 +27,7 @@
-module(rebar_templater).
-export(['create-app'/2,
+ 'create-lib'/2,
'create-node'/2,
'list-templates'/2,
create/2]).
@@ -50,6 +51,10 @@
%% Alias for create w/ template=simpleapp
create1(Config, "simpleapp").
+'create-lib'(Config, _File) ->
+ %% Alias for create w/ template=simplelib
+ create1(Config, "simplelib").
+
'create-node'(Config, _File) ->
%% Alias for create w/ template=simplenode
create1(Config, "simplenode").
@@ -98,7 +103,7 @@ render(Bin, Context) ->
ReOpts = [global, {return, list}],
Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts),
Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts),
- mustache:render(Str1, Context).
+ rebar_mustache:render(Str1, Context).
%% ===================================================================
%% Internal functions
@@ -116,6 +121,12 @@ info(help, 'create-app') ->
"~n"
"Valid command line options:~n"
" [appid=myapp]~n", []);
+info(help, 'create-lib') ->
+ ?CONSOLE(
+ "Create simple lib skel.~n"
+ "~n"
+ "Valid command line options:~n"
+ " [libid=mylib]~n", []);
info(help, 'create-node') ->
?CONSOLE(
"Create simple node skel.~n"
@@ -347,6 +358,10 @@ write_file(Output, Data, Force) ->
{error, exists}
end.
+prepend_instructions(Instructions, Rest) when is_list(Instructions) ->
+ Instructions ++ Rest;
+prepend_instructions(Instruction, Rest) ->
+ [Instruction|Rest].
%%
%% Execute each instruction in a template definition file.
@@ -364,6 +379,23 @@ execute_template(_Files, [], _TemplateType, _TemplateName,
?ERROR("One or more files already exist on disk and "
"were not generated:~n~s~s", [Msg , Help])
end;
+execute_template(Files, [{'if', Cond, True} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
+ execute_template(Files, [{'if', Cond, True, []}|Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles);
+execute_template(Files, [{'if', Cond, True, False} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
+ Instructions = case dict:find(Cond, Context) of
+ {ok, true} ->
+ True;
+ {ok, "true"} ->
+ True;
+ _ ->
+ False
+ end,
+ execute_template(Files, prepend_instructions(Instructions, Rest),
+ TemplateType, TemplateName, Context, Force,
+ ExistingFiles);
execute_template(Files, [{template, Input, Output} | Rest], TemplateType,
TemplateName, Context, Force, ExistingFiles) ->
InputName = filename:join(filename:dirname(TemplateName), Input),
diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl
index d18603c..3a38a08 100644
--- a/src/rebar_upgrade.erl
+++ b/src/rebar_upgrade.erl
@@ -87,7 +87,8 @@ info(help, 'generate-upgrade') ->
?CONSOLE("Build an upgrade package.~n"
"~n"
"Valid command line options:~n"
- " previous_release=path~n",
+ " previous_release=path~n"
+ " target_dir=target_dir (optional)~n",
[]).
run_checks(Config, OldVerPath, ReltoolConfig) ->
@@ -97,10 +98,7 @@ run_checks(Config, OldVerPath, ReltoolConfig) ->
{Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
- NewVerPath =
- filename:join(
- [rebar_rel_utils:get_target_parent_dir(Config, ReltoolConfig),
- Name]),
+ NewVerPath = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
true = rebar_utils:prop_check(filelib:is_dir(NewVerPath),
"Release directory doesn't exist (~p)~n",
[NewVerPath]),
@@ -184,13 +182,24 @@ boot_files(TargetDir, Ver, Name) ->
filename:join([TargetDir, "releases", Ver, "start_clean.boot"]),
filename:join([".", ?TMP, "releases", Ver, "start_clean.boot"])),
- {ok, _} = file:copy(
- filename:join([TargetDir, "releases", Ver, "sys.config"]),
- filename:join([".", ?TMP, "releases", Ver, "sys.config"])),
-
- {ok, _} = file:copy(
- filename:join([TargetDir, "releases", Ver, "vm.args"]),
- filename:join([".", ?TMP, "releases", Ver, "vm.args"])).
+ SysConfig = filename:join([TargetDir, "releases", Ver, "sys.config"]),
+ _ = case filelib:is_regular(SysConfig) of
+ true ->
+ {ok, _} = file:copy(
+ SysConfig,
+ filename:join([".", ?TMP, "releases", Ver,
+ "sys.config"]));
+ false -> ok
+ end,
+
+ VmArgs = filename:join([TargetDir, "releases", Ver, "vm.args"]),
+ case filelib:is_regular(VmArgs) of
+ true ->
+ {ok, _} = file:copy(
+ VmArgs,
+ filename:join([".", ?TMP, "releases", Ver, "vm.args"]));
+ false -> {ok, 0}
+ end.
make_tar(NameVer, NewVer, NewName) ->
Filename = NameVer ++ ".tar.gz",
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index fd93f98..2d227b6 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -52,6 +52,7 @@
erl_opts/1,
src_dirs/1,
ebin_dir/0,
+ base_dir/1,
processing_base_dir/1, processing_base_dir/2]).
-include("rebar.hrl").
@@ -108,6 +109,7 @@ sh(Command0, Options0) ->
Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])),
PortSettings = proplists:get_all_values(port_settings, Options) ++
[exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide],
+ ?DEBUG("Port Cmd: ~p\nPort Opts: ~p\n", [Command, PortSettings]),
Port = open_port({spawn, Command}, PortSettings),
case sh_loop(Port, OutputHandler, []) of
@@ -191,20 +193,20 @@ 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}, unicode],
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
end.
-vcs_vsn(Config, Vcs, Dir) ->
- Key = {Vcs, Dir},
+vcs_vsn(Config, Vsn, Dir) ->
+ Key = {Vsn, Dir},
Cache = rebar_config:get_xconf(Config, vsn_cache),
case dict:find(Key, Cache) of
error ->
- VsnString = vcs_vsn_1(Vcs, Dir),
+ VsnString = vcs_vsn_1(Vsn, Dir),
Cache1 = dict:store(Key, VsnString, Cache),
Config1 = rebar_config:set_xconf(Config, vsn_cache, Cache1),
{Config1, VsnString};
@@ -306,12 +308,15 @@ src_dirs(SrcDirs) ->
ebin_dir() ->
filename:join(get_cwd(), "ebin").
+base_dir(Config) ->
+ rebar_config:get_xconf(Config, base_dir).
+
processing_base_dir(Config) ->
Cwd = rebar_utils:get_cwd(),
processing_base_dir(Config, Cwd).
processing_base_dir(Config, Dir) ->
- Dir =:= rebar_config:get_xconf(Config, base_dir).
+ Dir =:= base_dir(Config).
%% ====================================================================
%% Internal functions
@@ -440,11 +445,12 @@ emulate_escript_foldl(Fun, Acc, File) ->
vcs_vsn_1(Vcs, Dir) ->
case vcs_vsn_cmd(Vcs) of
- {unknown, VsnString} ->
- ?DEBUG("vcs_vsn: Unknown VCS atom in vsn field: ~p\n", [Vcs]),
+ {plain, VsnString} ->
VsnString;
{cmd, CmdString} ->
vcs_vsn_invoke(CmdString, Dir);
+ unknown ->
+ ?ABORT("vcs_vsn: Unknown vsn format: ~p\n", [Vcs]);
Cmd ->
%% If there is a valid VCS directory in the application directory,
%% use that version info
@@ -477,7 +483,8 @@ vcs_vsn_cmd(bzr) -> "bzr revno";
vcs_vsn_cmd(svn) -> "svnversion";
vcs_vsn_cmd(fossil) -> "fossil info";
vcs_vsn_cmd({cmd, _Cmd}=Custom) -> Custom;
-vcs_vsn_cmd(Version) -> {unknown, Version}.
+vcs_vsn_cmd(Version) when is_list(Version) -> {plain, Version};
+vcs_vsn_cmd(_) -> unknown.
vcs_vsn_invoke(Cmd, Dir) ->
{ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl
index 8c0a872..16e8cc4 100644
--- a/src/rebar_xref.erl
+++ b/src/rebar_xref.erl
@@ -51,7 +51,7 @@ xref(Config, _) ->
xref:set_default(xref, [{warnings,
rebar_config:get(Config, xref_warnings, false)},
- {verbose, rebar_config:is_verbose(Config)}]),
+ {verbose, rebar_log:is_verbose(Config)}]),
{ok, _} = xref:add_directory(xref, "ebin"),
@@ -60,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, []),
@@ -92,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 ->
@@ -110,35 +103,31 @@ info(help, xref) ->
"Valid rebar.config options:~n"
" ~p~n"
" ~p~n"
+ " ~p~n"
" ~p~n",
[
{xref_warnings, false},
- {xref_checks, [exports_not_used, undefined_function_calls]},
+ {xref_extra_paths,[]},
+ {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\"))",[]}]}
]).
-check_exports_not_used() ->
- {ok, UnusedExports0} = xref:analyze(xref, exports_not_used),
- UnusedExports = filter_away_ignored(UnusedExports0),
-
- %% 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),
@@ -157,8 +146,9 @@ code_path(Config) ->
%% functions, even though those functions are present as part
%% of compilation. H/t to @dluna. Long term we should tie more
%% properly into the overall compile code path if possible.
- BaseDir = rebar_config:get_xconf(Config, base_dir),
+ BaseDir = rebar_utils:base_dir(Config),
[P || P <- code:get_path() ++
+ rebar_config:get(Config, xref_extra_paths, []) ++
[filename:join(BaseDir, filename:join(SubDir, "ebin"))
|| SubDir <- rebar_config:get(Config, sub_dirs, [])],
filelib:is_dir(P)].
@@ -166,41 +156,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
@@ -213,14 +260,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