summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rebar.erl10
-rw-r--r--src/rebar_app_info.erl18
-rw-r--r--src/rebar_cleaner.erl56
-rw-r--r--src/rebar_cover_utils.erl261
-rw-r--r--src/rebar_ct.erl388
-rw-r--r--src/rebar_edoc.erl130
-rw-r--r--src/rebar_eunit.erl675
-rw-r--r--src/rebar_fetch.erl7
-rw-r--r--src/rebar_getopt.erl914
-rw-r--r--src/rebar_otp_app.erl2
-rw-r--r--src/rebar_prv_app_builder.erl5
-rw-r--r--src/rebar_prv_escripter.erl (renamed from src/rebar_escripter.erl)2
-rw-r--r--src/rebar_prv_install_deps.erl110
-rw-r--r--src/rebar_require_vsn.erl122
14 files changed, 97 insertions, 2603 deletions
diff --git a/src/rebar.erl b/src/rebar.erl
index 01dca79..48a3ac6 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -189,10 +189,10 @@ run_aux(BaseConfig, Commands) ->
%%
help() ->
OptSpecList = option_spec_list(),
- rebar_getopt:usage(OptSpecList, "rebar",
- "[var=value,...] <command,...>",
- [{"var=value", "rebar global variables (e.g. force=1)"},
- {"command", "Command to run (e.g. compile)"}]),
+ 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(
@@ -206,7 +206,7 @@ help() ->
parse_args(RawArgs) ->
%% Parse getopt options
OptSpecList = option_spec_list(),
- case rebar_getopt:parse(OptSpecList, RawArgs) of
+ case getopt:parse(OptSpecList, RawArgs) of
{ok, Args} ->
Args;
{error, {Reason, Data}} ->
diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl
index a08ca92..698ebd7 100644
--- a/src/rebar_app_info.erl
+++ b/src/rebar_app_info.erl
@@ -86,14 +86,30 @@ config(AppInfo=#app_info_t{}, Config) ->
AppInfo#app_info_t{config=Config}.
-spec app_file_src(t()) -> file:name().
+app_file_src(#app_info_t{app_file_src=undefined, dir=Dir, name=Name}) ->
+ AppFileSrc = filename:join([Dir, "src", <<Name/binary, ".app.src">>]),
+ case filelib:is_file(AppFileSrc) of
+ true ->
+ ec_cnv:to_list(AppFileSrc);
+ false ->
+ undefined
+ end;
app_file_src(#app_info_t{app_file_src=AppFileSrc}) ->
- AppFileSrc.
+ ec_cnv:to_list(AppFileSrc).
-spec app_file_src(t(), file:name()) -> t().
app_file_src(AppInfo=#app_info_t{}, AppFileSrc) ->
AppInfo#app_info_t{app_file_src=AppFileSrc}.
-spec app_file(t()) -> file:name().
+app_file(#app_info_t{app_file=undefined, dir=Dir, name=Name}) ->
+ AppFile = filename:join([Dir, "ebin", <<Name/binary, ".app">>]),
+ case filelib:is_file(AppFile) of
+ true ->
+ AppFile;
+ false ->
+ undefined
+ end;
app_file(#app_info_t{app_file=AppFile}) ->
AppFile.
diff --git a/src/rebar_cleaner.erl b/src/rebar_cleaner.erl
deleted file mode 100644
index 7a762f5..0000000
--- a/src/rebar_cleaner.erl
+++ /dev/null
@@ -1,56 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% -------------------------------------------------------------------
--module(rebar_cleaner).
-
--export([clean/2]).
-
-%% for internal use only
--export([info/2]).
-
--include("rebar.hrl").
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-clean(Config, _AppFile) ->
- %% Get a list of files to delete from config and remove them
- FilesToClean = rebar_config:get(Config, clean_files, []),
- lists:foreach(fun (F) -> rebar_file_utils:rm_rf(F) end, FilesToClean).
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
-
-info(help, clean) ->
- ?CONSOLE(
- "Delete list of files.~n"
- "~n"
- "Valid rebar.config options:~n"
- " ~p~n",
- [
- {clean_files, ["file", "file2"]}
- ]).
diff --git a/src/rebar_cover_utils.erl b/src/rebar_cover_utils.erl
deleted file mode 100644
index 3195fe2..0000000
--- a/src/rebar_cover_utils.erl
+++ /dev/null
@@ -1,261 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
-%% Copyright (c) 2013 Andras Horvath (andras.horvath@erlang-solutions.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% -------------------------------------------------------------------
--module(rebar_cover_utils).
-
-%% for internal use only
--export([init/3,
- perform_cover/4,
- close/1,
- exit/0]).
-
--include("rebar.hrl").
-
-%% ====================================================================
-%% Internal functions
-%% ====================================================================
-
-perform_cover(Config, BeamFiles, SrcModules, TargetDir) ->
- perform_cover(rebar_config:get(Config, cover_enabled, false),
- Config, BeamFiles, SrcModules, TargetDir).
-
-perform_cover(false, _Config, _BeamFiles, _SrcModules, _TargetDir) ->
- ok;
-perform_cover(true, Config, BeamFiles, SrcModules, TargetDir) ->
- analyze(Config, BeamFiles, SrcModules, TargetDir).
-
-close(not_enabled) ->
- ok;
-close(F) ->
- ok = file:close(F).
-
-exit() ->
- cover:stop().
-
-init(false, _BeamFiles, _TargetDir) ->
- {ok, not_enabled};
-init(true, BeamFiles, TargetDir) ->
- %% Attempt to start the cover server, then set its group leader to
- %% TargetDir/cover.log, so all cover log messages will go there instead of
- %% to stdout. If the cover server is already started, we'll kill that
- %% server and start a new one in order not to inherit a polluted
- %% cover_server state.
- {ok, CoverPid} = case whereis(cover_server) of
- undefined ->
- cover:start();
- _ ->
- cover:stop(),
- cover:start()
- end,
-
- {ok, F} = OkOpen = file:open(
- filename:join([TargetDir, "cover.log"]),
- [write]),
-
- group_leader(F, CoverPid),
-
- ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
-
- Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles],
- case [Module || {_, {ok, Module}} <- Compiled] of
- [] ->
- %% No modules compiled successfully...fail
- ?ERROR("Cover failed to compile any modules; aborting.~n", []),
- ?FAIL;
- _ ->
- %% At least one module compiled successfully
-
- %% It's not an error for cover compilation to fail partially,
- %% but we do want to warn about them
- PrintWarning =
- fun(Beam, Desc) ->
- ?CONSOLE("Cover compilation warning for ~p: ~p",
- [Beam, Desc])
- end,
- _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled],
- OkOpen
- end;
-init(Config, BeamFiles, TargetDir) ->
- init(rebar_config:get(Config, cover_enabled, false), BeamFiles, TargetDir).
-
-analyze(_Config, [], _SrcModules, _TargetDir) ->
- ok;
-analyze(Config, FilteredModules, SrcModules, TargetDir) ->
- %% Generate coverage info for all the cover-compiled modules
- Coverage = lists:flatten([analyze_mod(M)
- || M <- FilteredModules,
- cover:is_compiled(M) =/= false]),
-
- %% Write index of coverage info
- write_index(lists:sort(Coverage), SrcModules, TargetDir),
-
- %% Write coverage details for each file
- lists:foreach(
- fun({M, _, _}) ->
- {ok, _} = cover:analyze_to_file(M,
- cover_file(M, TargetDir),
- [html])
- end, Coverage),
-
- Index = filename:join([rebar_utils:get_cwd(), TargetDir, "index.html"]),
- ?CONSOLE("Cover analysis: ~s\n", [Index]),
-
- %% Export coverage data, if configured
- case rebar_config:get(Config, cover_export_enabled, false) of
- true ->
- export_coverdata(TargetDir);
- false ->
- ok
- end,
-
- %% Print coverage report, if configured
- case rebar_config:get(Config, cover_print_enabled, false) of
- true ->
- print_coverage(lists:sort(Coverage));
- false ->
- ok
- end.
-
-analyze_mod(Module) ->
- case cover:analyze(Module, coverage, module) of
- {ok, {Module, {Covered, NotCovered}}} ->
- %% Modules that include the eunit header get an implicit
- %% test/0 fun, which cover considers a runnable line, but
- %% eunit:test(TestRepresentation) never calls. Decrement
- %% NotCovered in this case.
- [align_notcovered_count(Module, Covered, NotCovered,
- is_eunitized(Module))];
- {error, Reason} ->
- ?ERROR("Cover analyze failed for ~p: ~p ~p\n",
- [Module, Reason, code:which(Module)]),
- []
- end.
-
-is_eunitized(Mod) ->
- has_eunit_test_fun(Mod) andalso
- has_header(Mod, "include/eunit.hrl").
-
-has_eunit_test_fun(Mod) ->
- [F || {exports, Funs} <- Mod:module_info(),
- {F, 0} <- Funs, F =:= test] =/= [].
-
-has_header(Mod, Header) ->
- Mod1 = case code:which(Mod) of
- cover_compiled ->
- {file, File} = cover:is_compiled(Mod),
- File;
- non_existing -> Mod;
- preloaded -> Mod;
- L -> L
- end,
- {ok, {_, [{abstract_code, {_, AC}}]}} =
- beam_lib:chunks(Mod1, [abstract_code]),
- [F || {attribute, 1, file, {F, 1}} <- AC,
- string:str(F, Header) =/= 0] =/= [].
-
-align_notcovered_count(Module, Covered, NotCovered, false) ->
- {Module, Covered, NotCovered};
-align_notcovered_count(Module, Covered, NotCovered, true) ->
- {Module, Covered, NotCovered - 1}.
-
-write_index(Coverage, SrcModules, TargetDir) ->
- {ok, F} = file:open(filename:join([TargetDir, "index.html"]), [write]),
- 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),
- write_index_section(F, "Source", SrcCoverage),
- write_index_section(F, "Test", TestCoverage),
- ok = file:write(F, "</body></html>"),
- ok = file:close(F).
-
-write_index_section(_F, _SectionName, []) ->
- ok;
-write_index_section(F, SectionName, Coverage) ->
- %% Calculate total coverage
- {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
- {CAcc + C, NAcc + N}
- end, {0, 0}, Coverage),
- TotalCoverage = percentage(Covered, NotCovered),
-
- %% Write the report
- 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"),
-
- FmtLink =
- fun(Module, Cov, NotCov) ->
- ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n",
- [Module, Module, percentage(Cov, NotCov)])
- end,
- lists:foreach(fun({Module, Cov, NotCov}) ->
- ok = file:write(F, FmtLink(Module, Cov, NotCov))
- end, Coverage),
- ok = file:write(F, "</table>\n").
-
-print_coverage(Coverage) ->
- {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
- {CAcc + C, NAcc + N}
- end, {0, 0}, Coverage),
- TotalCoverage = percentage(Covered, NotCovered),
-
- %% Determine the longest module name for right-padding
- Width = lists:foldl(fun({Mod, _, _}, Acc) ->
- case length(atom_to_list(Mod)) of
- N when N > Acc ->
- N;
- _ ->
- Acc
- end
- end, 0, Coverage) * -1,
-
- %% Print the output the console
- ?CONSOLE("~nCode Coverage:~n", []),
- lists:foreach(fun({Mod, C, N}) ->
- ?CONSOLE("~*s : ~3s~n",
- [Width, Mod, percentage(C, N)])
- end, Coverage),
- ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]).
-
-cover_file(Module, TargetDir) ->
- filename:join([TargetDir, atom_to_list(Module) ++ ".COVER.html"]).
-
-export_coverdata(TargetDir) ->
- ExportFile = filename:join(TargetDir, "cover.coverdata"),
- case cover:export(ExportFile) of
- ok ->
- ?CONSOLE("Coverdata export: ~s~n", [ExportFile]);
- {error, Reason} ->
- ?ERROR("Coverdata export failed: ~p~n", [Reason])
- end.
-
-percentage(0, 0) ->
- "not executed";
-percentage(Cov, NotCov) ->
- integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%".
diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl
deleted file mode 100644
index b5abeae..0000000
--- a/src/rebar_ct.erl
+++ /dev/null
@@ -1,388 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% -------------------------------------------------------------------
-%%
-%% Targets:
-%% test - run common test suites in ./test
-%% int_test - run suites in ./int_test
-%% perf_test - run suites inm ./perf_test
-%%
-%% Global options:
-%% verbose=1 - show output from the common_test run as it goes
-%% suites="foo,bar" - run <test>/foo_SUITE and <test>/bar_SUITE
-%% case="mycase" - run individual test case foo_SUITE:mycase
-%% -------------------------------------------------------------------
--module(rebar_ct).
-
--behaviour(rebar_provider).
-
--export([init/1,
- do/1]).
-
-%% for internal use only
--export([info/2]).
-
--include("rebar.hrl").
-
--define(PROVIDER, ct).
--define(DEPS, [compile]).
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
--spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
-init(State) ->
- State1 = rebar_state:add_provider(State, #provider{name = ?PROVIDER,
- provider_impl = ?MODULE,
- bare = false,
- deps = ?DEPS,
- example = "rebar ct",
- short_desc = "",
- desc = "",
- opts = []}),
- {ok, State1}.
-
--spec do(rebar_state:t()) -> {ok, rebar_state:t()}.
-do(State) ->
- TestDir = rebar_state:get(State, ct_dir, "test"),
- LogDir = rebar_state:get(State, ct_log_dir, "logs"),
- run_test_if_present(TestDir, LogDir, State),
- {ok, State}.
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
-
-info(help, ct) ->
- ?CONSOLE(
- "Run common_test suites.~n"
- "~n"
- "Valid rebar.config options:~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- "Valid command line options:~n"
- " suites=foo,bar - run <test>/foo_SUITE and <test>/bar_SUITE~n"
- " case=\"mycase\" - run individual test case foo_SUITE:mycase~n",
- [
- {ct_dir, "itest"},
- {ct_log_dir, "test/logs"},
- {ct_extra_params, "-boot start_sasl -s myapp"},
- {ct_use_short_names, true}
- ]).
-
-run_test_if_present(TestDir, LogDir, State) ->
- case filelib:is_dir(TestDir) of
- false ->
- ?WARN("~s directory not present - skipping\n", [TestDir]),
- ok;
- true ->
- case filelib:wildcard(TestDir ++ "/*_SUITE.{beam,erl}") of
- [] ->
- ?WARN("~s directory present, but no common_test"
- ++ " SUITES - skipping\n", [TestDir]),
- ok;
- _ ->
- try
- run_test(TestDir, LogDir, State)
- catch
- throw:skip ->
- ok
- end
- end
- end.
-
-run_test(TestDir, LogDir, State) ->
- {Cmd, RawLog} = make_cmd(TestDir, LogDir, State),
- ?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
- clear_log(LogDir, RawLog),
- Output = case rebar_log:is_verbose(State) of
- false ->
- " >> " ++ RawLog ++ " 2>&1";
- true ->
- " 2>&1 | tee -a " ++ RawLog
- end,
-
- ShOpts = [{env,[{"TESTDIR", TestDir}]}, return_on_error],
- case rebar_utils:sh(Cmd ++ Output, ShOpts) of
- {ok,_} ->
- %% in older versions of ct_run, this could have been a failure
- %% that returned a non-0 code. Check for that!
- check_success_log(State, RawLog);
- {error,Res} ->
- %% In newer ct_run versions, this may be a sign of a good compile
- %% that failed cases. In older version, it's a worse error.
- check_fail_log(State, RawLog, Cmd ++ Output, Res)
- end.
-
-clear_log(LogDir, RawLog) ->
- case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
- ok ->
- NowStr = rebar_utils:now_str(),
- LogHeader = "--- Test run on " ++ NowStr ++ " ---\n",
- ok = file:write_file(RawLog, LogHeader);
- {error, Reason} ->
- ?ERROR("Could not create log dir - ~p\n", [Reason]),
- ?FAIL
- end.
-
-%% calling ct with erl does not return non-zero on failure - have to check
-%% log results
-check_success_log(State, RawLog) ->
- check_log(State, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
-
--type err_handler() :: fun((string()) -> no_return()).
--spec failure_logger(string(), {integer(), string()}) -> err_handler().
-failure_logger(Command, {Rc, Output}) ->
- fun(_Msg) ->
- ?ABORT("~s failed with error: ~w and output:~n~s~n",
- [Command, Rc, Output])
- end.
-
-check_fail_log(State, RawLog, Command, Result) ->
- check_log(State, RawLog, failure_logger(Command, Result)).
-
-check_log(State,RawLog,Fun) ->
- {ok, Msg} =
- 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,
- if
- MakeFailed ->
- show_log(State, RawLog),
- ?ERROR("Building tests failed\n",[]),
- ?FAIL;
-
- RunFailed ->
- show_log(State, RawLog),
- ?ERROR("One or more tests failed\n",[]),
- ?FAIL;
-
- true ->
- Fun(Msg)
- end.
-
-
-%% Show the log if it hasn't already been shown because verbose was on
-show_log(State, RawLog) ->
- ?CONSOLE("Showing log\n", []),
- case rebar_log:is_verbose(State) of
- false ->
- {ok, Contents} = file:read_file(RawLog),
- ?CONSOLE("~s", [Contents]);
- true ->
- ok
- end.
-
-make_cmd(TestDir, RawLogDir, State) ->
- Cwd = rebar_utils:get_cwd(),
- LogDir = filename:join(Cwd, RawLogDir),
- IncludeDir = filename:join(Cwd, "include"),
- Include = case filelib:is_dir(IncludeDir) of
- true ->
- " -include \"" ++ IncludeDir ++ "\"";
- false ->
- ""
- 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
- %% avoid duplication
- Apps = rebar_state:apps_to_build(State),
- DepsDir = rebar_prv_install_deps:get_deps_dir(State),
- DepsDirEbin = filename:join([DepsDir, "*", "ebin"]),
- AppDirs = [filename:join(rebar_app_info:dir(A), "ebin") || A <- Apps],
- CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [DepsDirEbin | AppDirs]],
- CodePathString = string:join(CodeDirs, " "),
- Cmd = case get_ct_specs(State, Cwd) of
- undefined ->
- ?FMT("~s"
- " -pa ~s"
- " ~s"
- " ~s"
- " -logdir \"~s\""
- " -env TEST_DIR \"~s\"",
- [BaseCmd,
- CodePathString,
- Include,
- build_name(State),
- LogDir,
- filename:join(Cwd, TestDir)]) ++
- get_cover_config(State, Cwd) ++
- get_ct_config_file(TestDir) ++
- get_config_file(TestDir) ++
- get_suites(State, TestDir) ++
- get_case(State);
- SpecFlags ->
- ?FMT("~s"
- " -pa ~s"
- " ~s"
- " ~s"
- " -logdir \"~s\""
- " -env TEST_DIR \"~s\"",
- [BaseCmd,
- CodePathString,
- Include,
- build_name(State),
- LogDir,
- filename:join(Cwd, TestDir)]) ++
- SpecFlags ++ get_cover_config(State, Cwd)
- end,
- io:format("Cmd ~s~n", [Cmd]),
- Cmd1 = Cmd ++ get_extra_params(State),
- RawLog = filename:join(LogDir, "raw.log"),
- {Cmd1, RawLog}.
-
-build_name(State) ->
- case rebar_state:get(State, ct_use_short_names, false) of
- true -> "-sname test";
- false -> " -name test@" ++ net_adm:localhost()
- end.
-
-get_extra_params(State) ->
- case rebar_state:get(State, ct_extra_params, undefined) of
- undefined ->
- "";
- Defined ->
- " " ++ Defined
- end.
-
-get_ct_specs(State, Cwd) ->
- case collect_glob(State, Cwd, ".*\.test\.spec\$") of
- [] -> undefined;
- [Spec] ->
- " -spec " ++ Spec;
- Specs ->
- " -spec " ++
- lists:flatten([io_lib:format("~s ", [Spec]) || Spec <- Specs])
- end.
-
-get_cover_config(State, Cwd) ->
- case rebar_state:get(State, cover_enabled, false) of
- false ->
- "";
- true ->
- case collect_glob(State, Cwd, ".*cover\.spec\$") of
- [] ->
- ?DEBUG("No cover spec found: ~s~n", [Cwd]),
- "";
- [Spec] ->
- ?DEBUG("Found cover file ~s~n", [Spec]),
- " -cover " ++ Spec;
- Specs ->
- ?ABORT("Multiple cover specs found: ~p~n", [Specs])
- end
- end.
-
-collect_glob(State, Cwd, Glob) ->
- DepsDir = rebar_prv_install_deps:get_deps_dir(State),
- CwdParts = filename:split(Cwd),
- filelib:fold_files(Cwd, Glob, true, fun(F, Acc) ->
- %% Ignore any specs under the deps/ directory. Do this pulling
- %% the dirname off the F and then splitting it into a list.
- Parts = filename:split(filename:dirname(F)),
- Parts2 = remove_common_prefix(Parts, CwdParts),
- case lists:member(DepsDir, Parts2) of
- true ->
- Acc; % There is a directory named "deps" in path
- false ->
- [F | Acc] % No "deps" directory in path
- end
- end, []).
-
-remove_common_prefix([H1|T1], [H1|T2]) ->
- remove_common_prefix(T1, T2);
-remove_common_prefix(L1, _) ->
- L1.
-
-get_ct_config_file(TestDir) ->
- State = filename:join(TestDir, "test.config"),
- case filelib:is_regular(State) of
- false ->
- " ";
- true ->
- " -ct_config " ++ State
- end.
-
-get_config_file(TestDir) ->
- State = filename:join(TestDir, "app.config"),
- case filelib:is_regular(State) of
- false ->
- " ";
- true ->
- " -config " ++ State
- end.
-
-get_suites(State, TestDir) ->
- case get_suites(State) of
- undefined ->
- " -dir " ++ TestDir;
- Suites ->
- Suites1 = string:tokens(Suites, ","),
- Suites2 = [find_suite_path(Suite, TestDir) || Suite <- Suites1],
- string:join([" -suite"] ++ Suites2, " ")
- end.
-
-get_suites(State) ->
- case rebar_state:get(State, suites, undefined) of
- undefined ->
- rebar_state:get(State, suite, undefined);
- Suites ->
- Suites
- end.
-
-find_suite_path(Suite, TestDir) ->
- Path = filename:join(TestDir, Suite ++ "_SUITE.erl"),
- case filelib:is_regular(Path) of
- false ->
- ?WARN("Suite ~s not found\n", [Suite]),
- %% Note - this throw is caught in run_test_if_present/3;
- %% this solution was easier than refactoring the entire module.
- throw(skip);
- true ->
- Path
- end.
-
-get_case(State) ->
- case rebar_state:get(State, 'case', undefined) of
- undefined ->
- "";
- Case ->
- " -case " ++ Case
- end.
diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl
deleted file mode 100644
index c828d27..0000000
--- a/src/rebar_edoc.erl
+++ /dev/null
@@ -1,130 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2010 Dave Smith (dizzyd@dizzyd.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% -------------------------------------------------------------------
-%% @author Dave Smith <dizzyd@dizzyd.com>
-%% @doc rebar_edoc supports the following command:
-%% <ul>
-%% <li>doc (essentially erl -noshell -run edoc_run application
-%% "'$(&lt;app_name&gt;)'"
-%% '"."' '[&lt;options&gt;]')</li>
-%% </ul>
-%% EDoc options can be given in the <code>edoc_opts</code> option in
-%% <code>rebar.config</code>.
-%% @copyright 2010 Dave Smith
-%% -------------------------------------------------------------------
--module(rebar_edoc).
-
--export([doc/2]).
-
-%% for internal use only
--export([info/2]).
-
--include("rebar.hrl").
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
-doc(Config, File) ->
- %% Save code path
- CodePath = setup_code_path(),
-
- %% Get the edoc_opts and app file info
- EDocOpts = rebar_config:get(Config, edoc_opts, []),
- {ok, Config1, AppName, _AppData} =
- rebar_app_utils:load_app_file(Config, File),
-
- case needs_regen(EDocOpts) of
- true ->
- ?INFO("Regenerating edocs for ~p\n", [AppName]),
- ok = edoc:application(AppName, ".", EDocOpts);
- false ->
- ?INFO("Skipping regeneration of edocs for ~p\n", [AppName]),
- ok
- end,
-
- %% Restore code path
- true = code:set_path(CodePath),
- {ok, Config1}.
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
-
-info(help, doc) ->
- ?CONSOLE(
- "Generate Erlang program documentation.~n"
- "~n"
- "Valid rebar.config options:~n"
- " {edoc_opts, []} (see edoc:application/3 documentation)~n",
- []).
-
-setup_code_path() ->
- %% Setup code path prior to calling edoc so that edown, asciiedoc,
- %% and the like can work properly when generating their own
- %% documentation.
- CodePath = code:get_path(),
- true = code:add_patha(rebar_utils:ebin_dir()),
- CodePath.
-
--type path_spec() :: {'file', file:filename()} | file:filename().
--spec newer_file_exists(Paths::[path_spec()], OldFile::string()) -> boolean().
-newer_file_exists(Paths, OldFile) ->
- OldModTime = filelib:last_modified(OldFile),
-
- ThrowIfNewer = fun(Fn, _Acc) ->
- FModTime = filelib:last_modified(Fn),
- (FModTime > OldModTime) andalso
- throw({newer_file_exists, {Fn, FModTime}})
- end,
-
- try
- lists:foldl(fun({file, F}, _) ->
- ThrowIfNewer(F, false);
- (P, _) ->
- filelib:fold_files(P, ".*.erl", true,
- ThrowIfNewer, false)
- end, undefined, Paths)
- catch
- throw:{newer_file_exists, {Filename, FMod}} ->
- ?DEBUG("~p is more recent than ~p: "
- "~120p > ~120p\n",
- [Filename, OldFile, FMod, OldModTime]),
- true
- end.
-
-%% Needs regen if any dependent file is changed since the last
-%% edoc run. Dependent files are the erlang source files,
-%% and the overview file, if it exists.
--spec needs_regen(proplists:proplist()) -> boolean().
-needs_regen(EDocOpts) ->
- DocDir = proplists:get_value(dir, EDocOpts, "doc"),
- EDocInfoName = filename:join(DocDir, "edoc-info"),
- OverviewFile = proplists:get_value(overview, EDocOpts, "overview.edoc"),
- EDocOverviewName = filename:join(DocDir, OverviewFile),
- SrcPaths = proplists:get_value(source_path, EDocOpts, ["src"]),
-
- newer_file_exists([{file, EDocOverviewName} | SrcPaths], EDocInfoName).
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
deleted file mode 100644
index 39dd35d..0000000
--- a/src/rebar_eunit.erl
+++ /dev/null
@@ -1,675 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%% -------------------------------------------------------------------
-%% @author Dave Smith <dizzyd@dizzyd.com>
-%% @doc rebar_eunit supports the following commands:
-%% <ul>
-%% <li>eunit - runs eunit tests</li>
-%% <li>clean - remove ?EUNIT_DIR directory</li>
-%% <li>reset_after_eunit::boolean() - default = true.
-%% If true, try to "reset" VM state to approximate state prior to
-%% running the EUnit tests:
-%% <ul>
-%% <li>Stop net_kernel if it was started</li>
-%% <li>Stop OTP applications not running before EUnit tests were run</li>
-%% <li>Kill processes not running before EUnit tests were run</li>
-%% <li>Reset OTP application environment variables</li>
-%% </ul>
-%% </li>
-%% </ul>
-%% The following Global options are supported:
-%% <ul>
-%% <li>verbose=1 - show extra output from the eunit test</li>
-%% <li>
-%% suites="foo,bar" - runs tests in foo.erl, test/foo_tests.erl and
-%% tests in bar.erl, test/bar_tests.erl
-%% </li>
-%% <li>
-%% suites="foo,bar" tests="baz"- runs first test with name starting
-%% with 'baz' in foo.erl, test/foo_tests.erl and tests in bar.erl,
-%% test/bar_tests.erl
-%% </li>
-%% <li>
-%% tests="baz"- 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
-%% </li>
-%% </ul>
-%% Additionally, for projects that have separate folders for the core
-%% implementation, and for the unit tests, then the following
-%% <code>rebar.config</code> option can be provided:
-%% <code>{eunit_compile_opts, [{src_dirs, ["src", "dir"]}]}.</code>.
-%% @copyright 2009, 2010 Dave Smith
-%% -------------------------------------------------------------------
--module(rebar_eunit).
-
--export([eunit/2,
- clean/2]).
-
-%% for internal use only
--export([info/2]).
-
--include("rebar.hrl").
-
--define(EUNIT_DIR, ".eunit").
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
-eunit(Config, _AppFile) ->
- ok = ensure_dirs(),
- %% Save code path
- CodePath = setup_code_path(),
- CompileOnly = rebar_config:get_global(Config, compile_only, false),
- {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "eunit",
- ?EUNIT_DIR),
- case CompileOnly of
- "true" ->
- true = code:set_path(CodePath),
- ?CONSOLE("Compiled modules for eunit~n", []);
- false ->
- run_eunit(Config, CodePath, SrcErls)
- end.
-
-clean(_Config, _File) ->
- rebar_file_utils:rm_rf(?EUNIT_DIR).
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
-
-info(help, eunit) ->
- info_help("Run eunit tests");
-info(help, clean) ->
- Description = ?FMT("Delete eunit test dir (~s)", [?EUNIT_DIR]),
- info_help(Description).
-
-info_help(Description) ->
- ?CONSOLE(
- "~s.~n"
- "~n"
- "Valid rebar.config options:~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- " ~p~n"
- "Valid command line options:~n"
- " suite[s]=\"foo,bar\" (Run tests in foo.erl, test/foo_tests.erl and~n"
- " tests in bar.erl, test/bar_tests.erl)~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"
- " 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, []},
- {eunit_compile_opts, []},
- {eunit_first_files, []},
- {cover_enabled, false},
- {cover_print_enabled, false},
- {cover_export_enabled, false}
- ]).
-
-run_eunit(Config, CodePath, SrcErls) ->
- %% Build a list of all the .beams in ?EUNIT_DIR -- use this for
- %% cover and eunit testing. Normally you can just tell cover
- %% and/or eunit to scan the directory for you, but eunit does a
- %% code:purge in conjunction with that scan and causes any cover
- %% compilation info to be lost.
-
- AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
- {BeamFiles, TestBeamFiles} =
- lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end,
- AllBeamFiles),
- OtherBeamFiles = TestBeamFiles --
- [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
- ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles),
-
- %% Get matching tests and modules
- AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
- {Tests, FilteredModules} =
- get_tests_and_modules(Config, ModuleBeamFiles, AllModules),
-
- SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
-
- {ok, CoverLog} = rebar_cover_utils:init(Config, ModuleBeamFiles,
- eunit_dir()),
-
- StatusBefore = status_before_eunit(),
- EunitResult = perform_eunit(Config, Tests),
-
- rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules,
- eunit_dir()),
- rebar_cover_utils:close(CoverLog),
-
- case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
- true) of
- true ->
- reset_after_eunit(StatusBefore);
- false ->
- ok
- end,
-
- %% Stop cover to clean the cover_server state. This is important if we want
- %% eunit+cover to not slow down when analyzing many Erlang modules.
- ok = rebar_cover_utils:exit(),
-
- case EunitResult of
- ok ->
- ok;
- _ ->
- ?ABORT("One or more eunit tests failed.~n", [])
- end,
-
- %% Restore code path
- true = code:set_path(CodePath),
- ok.
-
-ensure_dirs() ->
- %% Make sure ?EUNIT_DIR/ and ebin/ directory exists (append dummy module)
- ok = filelib:ensure_dir(filename:join(eunit_dir(), "dummy")),
- ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
-
-eunit_dir() ->
- filename:join(rebar_utils:get_cwd(), ?EUNIT_DIR).
-
-setup_code_path() ->
- %% Setup code path prior to compilation so that parse_transforms
- %% and the like work properly. Also, be sure to add ebin_dir()
- %% to the END of the code path so that we don't have to jump
- %% through hoops to access the .app file
- CodePath = code:get_path(),
- true = code:add_patha(eunit_dir()),
- true = code:add_pathz(rebar_utils:ebin_dir()),
- CodePath.
-
-%%
-%% == get matching tests ==
-%%
-get_tests_and_modules(Config, ModuleBeamFiles, AllModules) ->
- SelectedSuites = get_selected_suites(Config, AllModules),
- {Tests, QualifiedTests} = get_qualified_and_unqualified_tests(Config),
- Modules = get_test_modules(SelectedSuites, Tests,
- QualifiedTests, ModuleBeamFiles),
- FilteredModules = get_matching_modules(AllModules, Modules, QualifiedTests),
- MatchedTests = get_matching_tests(Modules, Tests, QualifiedTests),
- {MatchedTests, FilteredModules}.
-
-%%
-%% == get suites specified via 'suites' option ==
-%%
-get_selected_suites(Config, Modules) ->
- RawSuites = get_suites(Config),
- Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
- [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.
-
-get_qualified_and_unqualified_tests(Config) ->
- RawFunctions = rebar_config:get_global(Config, tests, ""),
- FunctionNames = [FunctionName ||
- FunctionName <- string:tokens(RawFunctions, ",")],
- get_qualified_and_unqualified_tests1(FunctionNames, [], []).
-
-get_qualified_and_unqualified_tests1([], Functions, QualifiedFunctions) ->
- {Functions, QualifiedFunctions};
-get_qualified_and_unqualified_tests1([TestName|TestNames], Functions,
- QualifiedFunctions) ->
- case string:tokens(TestName, ":") of
- [TestName] ->
- Function = list_to_atom(TestName),
- get_qualified_and_unqualified_tests1(
- TestNames, [Function|Functions], QualifiedFunctions);
- [ModuleName, FunctionName] ->
- M = list_to_atom(ModuleName),
- F = list_to_atom(FunctionName),
- get_qualified_and_unqualified_tests1(TestNames, Functions,
- [{M, F}|QualifiedFunctions]);
- _ ->
- ?ABORT("Unsupported test function specification: ~s~n", [TestName])
- end.
-
-%% Provide modules which are to be searched for tests.
-%% Several scenarios are possible:
-%%
-%% == 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 ==
-%% 1) Specific tests have been provided and/or
-%% no unqualified tests have been specified and
-%% there were some qualified tests, then we can search for
-%% functions in specified suites (or in empty set of suites).
-%%
-%% 2) Neither specific suites nor qualified test names have been
-%% provided use ModuleBeamFiles which filters out "*_tests"
-%% modules so EUnit won't doubly run them and cover only
-%% calculates coverage on production code. However,
-%% keep "*_tests" modules that are not automatically
-%% included by EUnit.
-%%
-%% From 'Primitives' in the EUnit User's Guide
-%% http://www.erlang.org/doc/apps/eunit/chapter.html
-%% "In addition, EUnit will also look for another
-%% module whose name is ModuleName plus the suffix
-%% _tests, and if it exists, all the tests from that
-%% module will also be added. (If ModuleName already
-%% contains the suffix _tests, this is not done.) E.g.,
-%% the specification {module, mymodule} will run all
-%% tests in the modules mymodule and mymodule_tests.
-%% Typically, the _tests module should only contain
-%% test cases that use the public interface of the main
-%% module (and no other code)."
-get_test_modules(SelectedSuites, Tests, QualifiedTests, ModuleBeamFiles) ->
- SuitesProvided = SelectedSuites =/= [],
- OnlyQualifiedTestsProvided = QualifiedTests =/= [] andalso Tests =:= [],
- if
- SuitesProvided orelse OnlyQualifiedTestsProvided ->
- SelectedSuites;
- true ->
- [rebar_utils:beam_to_mod(?EUNIT_DIR, N) ||
- N <- ModuleBeamFiles]
- end.
-
-get_matching_modules(AllModules, Modules, QualifiedTests) ->
- ModuleFilterMapper =
- fun({M, _}) ->
- case lists:member(M, AllModules) of
- true -> {true, M};
- _-> false
- end
- end,
- ModulesFromQualifiedTests = lists:zf(ModuleFilterMapper, QualifiedTests),
- lists:usort(Modules ++ ModulesFromQualifiedTests).
-
-get_matching_tests(Modules, [], []) ->
- Modules;
-get_matching_tests(Modules, [], QualifiedTests) ->
- FilteredQualifiedTests = filter_qualified_tests(Modules, QualifiedTests),
- lists:merge(Modules, make_test_primitives(FilteredQualifiedTests));
-get_matching_tests(Modules, Tests, QualifiedTests) ->
- AllTests = lists:merge(QualifiedTests,
- get_matching_tests1(Modules, Tests, [])),
- make_test_primitives(AllTests).
-
-filter_qualified_tests(Modules, QualifiedTests) ->
- TestsFilter = fun({Module, _Function}) ->
- lists:all(fun(M) -> M =/= Module end, Modules) end,
- lists:filter(TestsFilter, QualifiedTests).
-
-get_matching_tests1([], _Functions, TestFunctions) ->
- TestFunctions;
-
-get_matching_tests1([Module|TModules], Functions, TestFunctions) ->
- %% Get module exports
- ModuleStr = atom_to_list(Module),
- ModuleExports = get_beam_test_exports(ModuleStr),
- %% Get module _tests exports
- TestModuleStr = string:concat(ModuleStr, "_tests"),
- TestModuleExports = get_beam_test_exports(TestModuleStr),
- %% Build tests {M, F} list
- Tests = get_matching_tests2(Functions, {Module, ModuleExports},
- {list_to_atom(TestModuleStr),
- TestModuleExports}),
- get_matching_tests1(TModules, Functions,
- lists:merge([TestFunctions, Tests])).
-
-get_matching_tests2(Functions, {Mod, ModExports}, {TestMod, TestModExports}) ->
- %% Look for matching functions into ModExports
- ModExportsStr = [atom_to_list(E1) || E1 <- ModExports],
- TestModExportsStr = [atom_to_list(E2) || E2 <- TestModExports],
- get_matching_exports(Functions, {Mod, ModExportsStr},
- {TestMod, TestModExportsStr}, []).
-
-get_matching_exports([], _, _, Matched) ->
- Matched;
-get_matching_exports([Function|TFunctions], {Mod, ModExportsStr},
- {TestMod, TestModExportsStr}, Matched) ->
-
- FunctionStr = atom_to_list(Function),
- %% Get matching Function in module, otherwise look in _tests module
- NewMatch = case get_matching_export(FunctionStr, ModExportsStr) of
- [] ->
- {TestMod, get_matching_export(FunctionStr,
- TestModExportsStr)};
- MatchingExport ->
- {Mod, MatchingExport}
- end,
- case NewMatch of
- {_, []} ->
- get_matching_exports(TFunctions, {Mod, ModExportsStr},
- {TestMod, TestModExportsStr}, Matched);
- _ ->
- get_matching_exports(TFunctions, {Mod, ModExportsStr},
- {TestMod, TestModExportsStr},
- [NewMatch|Matched])
- end.
-
-get_matching_export(_FunctionStr, []) ->
- [];
-get_matching_export(FunctionStr, [ExportStr|TExportsStr]) ->
- case string:str(ExportStr, FunctionStr) of
- 1 ->
- list_to_atom(ExportStr);
- _ ->
- get_matching_export(FunctionStr, TExportsStr)
- end.
-
-get_beam_test_exports(ModuleStr) ->
- FilePath = filename:join(eunit_dir(),
- string:concat(ModuleStr, ".beam")),
- case filelib:is_regular(FilePath) of
- true ->
- {beam_file, _, Exports0, _, _, _} = beam_disasm:file(FilePath),
- Exports1 = [FunName || {FunName, FunArity, _} <- Exports0,
- FunArity =:= 0],
- F = fun(FName) ->
- FNameStr = atom_to_list(FName),
- re:run(FNameStr, "_test(_)?") =/= nomatch
- end,
- lists:filter(F, Exports1);
- _ ->
- []
- end.
-
-make_test_primitives(RawTests) ->
- %% Use {test,M,F} and {generator,M,F} if at least R15B02. Otherwise,
- %% use eunit_test:function_wrapper/2 fallback.
- %% eunit_test:function_wrapper/2 was renamed to eunit_test:mf_wrapper/2
- %% in R15B02; use that as >= R15B02 check.
- %% TODO: remove fallback and use only {test,M,F} and {generator,M,F}
- %% primitives once at least R15B02 is required.
- {module, eunit_test} = code:ensure_loaded(eunit_test),
- MakePrimitive = case erlang:function_exported(eunit_test, mf_wrapper, 2) of
- true -> fun eunit_primitive/3;
- false -> fun pre15b02_eunit_primitive/3
- end,
-
- ?CONSOLE(" Running test function(s):~n", []),
- F = fun({M, F2}, Acc) ->
- ?CONSOLE(" ~p:~p/0~n", [M, F2]),
- FNameStr = atom_to_list(F2),
- NewFunction =
- case re:run(FNameStr, "_test_") of
- nomatch ->
- %% Normal test
- MakePrimitive(test, M, F2);
- _ ->
- %% Generator
- MakePrimitive(generator, M, F2)
- end,
- [NewFunction|Acc]
- end,
- lists:foldl(F, [], RawTests).
-
-eunit_primitive(Type, M, F) ->
- {Type, M, F}.
-
-pre15b02_eunit_primitive(test, M, F) ->
- eunit_test:function_wrapper(M, F);
-pre15b02_eunit_primitive(generator, M, F) ->
- {generator, eunit_test:function_wrapper(M, F)}.
-
-%%
-%% == run tests ==
-%%
-
-perform_eunit(Config, Tests) ->
- EunitOpts = get_eunit_opts(Config),
-
- %% Move down into ?EUNIT_DIR while we run tests so any generated files
- %% are created there (versus in the source dir)
- Cwd = rebar_utils:get_cwd(),
- ok = file:set_cwd(?EUNIT_DIR),
-
- EunitResult = (catch eunit:test(Tests, EunitOpts)),
-
- %% Return to original working dir
- ok = file:set_cwd(Cwd),
-
- EunitResult.
-
-get_eunit_opts(Config) ->
- %% Enable verbose in eunit if so requested..
- BaseOpts = case rebar_log:is_verbose(Config) of
- true ->
- [verbose];
- false ->
- []
- end,
-
- BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []).
-
-%%
-%% == reset_after_eunit ==
-%%
-
-status_before_eunit() ->
- Apps = get_app_names(),
- AppEnvs = [{App, application:get_all_env(App)} || App <- Apps],
- {erlang:processes(), erlang:is_alive(), AppEnvs, ets:tab2list(ac_tab)}.
-
-get_app_names() ->
- [AppName || {AppName, _, _} <- application:loaded_applications()].
-
-reset_after_eunit({OldProcesses, WasAlive, OldAppEnvs, _OldACs}) ->
- IsAlive = erlang:is_alive(),
- if not WasAlive andalso IsAlive ->
- ?DEBUG("Stopping net kernel....\n", []),
- erl_epmd:stop(),
- _ = net_kernel:stop(),
- pause_until_net_kernel_stopped();
- true ->
- ok
- end,
-
- OldApps = [App || {App, _} <- OldAppEnvs],
- Apps = get_app_names(),
- _ = [begin
- _ = case lists:member(App, OldApps) of
- true -> ok;
- false -> application:stop(App)
- end,
- ok = application:unset_env(App, K)
- end || App <- Apps, App /= rebar,
- {K, _V} <- application:get_all_env(App),
- K =/= included_applications],
-
- reconstruct_app_env_vars(Apps),
-
- Processes = erlang:processes(),
- _ = kill_extras(Processes -- OldProcesses),
-
- ok.
-
-kill_extras(Pids) ->
- %% Killing any of the procs below will either:
- %% 1. Interfere with stuff that we don't want interfered with, or
- %% 2. May/will force the 'kernel' app to shutdown, which *will*
- %% interfere with rebar's ability To Do Useful Stuff(tm).
- %% This list may require changes as OTP versions and/or
- %% rebar use cases change.
- KeepProcs = [cover_server, eunit_server,
- eqc, eqc_license, eqc_locked,
- %% inet_gethost_native is started on demand, when
- %% doing name lookups. It is under kernel_sup, under
- %% a supervisor_bridge.
- inet_gethost_native],
- Killed = [begin
- Info = case erlang:process_info(Pid) of
- undefined -> [];
- Else -> Else
- end,
- Keep1 = case proplists:get_value(registered_name, Info) of
- undefined ->
- false;
- Name ->
- lists:member(Name, KeepProcs)
- end,
- Keep2 = case proplists:get_value(dictionary, Info) of
- undefined ->
- false;
- Ds ->
- case proplists:get_value('$ancestors', Ds) of
- undefined ->
- false;
- As ->
- lists:member(kernel_sup, As)
- end
- end,
- if Keep1 orelse Keep2 ->
- ok;
- true ->
- ?DEBUG("Kill ~p ~p\n", [Pid, Info]),
- exit(Pid, kill),
- Pid
- end
- end || Pid <- Pids],
- case lists:usort(Killed) -- [ok] of
- [] ->
- ?DEBUG("No processes to kill\n", []),
- [];
- Else ->
- lists:foreach(fun(Pid) -> wait_until_dead(Pid) end, Else),
- Else
- end.
-
-reconstruct_app_env_vars([App|Apps]) ->
- CmdLine0 = proplists:get_value(App, init:get_arguments(), []),
- CmdVars = [{list_to_atom(K), list_to_atom(V)} || {K, V} <- CmdLine0],
- AppFile = (catch filename:join([code:lib_dir(App),
- "ebin",
- atom_to_list(App) ++ ".app"])),
- AppVars = case file:consult(AppFile) of
- {ok, [{application, App, Ps}]} ->
- proplists:get_value(env, Ps, []);
- _ ->
- []
- end,
-
- %% App vars specified in config files override those in the .app file.
- %% Config files later in the args list override earlier ones.
- AppVars1 = case init:get_argument(config) of
- {ok, ConfigFiles} ->
- {App, MergedAppVars} = lists:foldl(fun merge_app_vars/2,
- {App, AppVars},
- ConfigFiles),
- MergedAppVars;
- error ->
- AppVars
- end,
- AllVars = CmdVars ++ AppVars1,
- ?DEBUG("Reconstruct ~p ~p\n", [App, AllVars]),
- lists:foreach(fun({K, V}) -> application:set_env(App, K, V) end, AllVars),
- reconstruct_app_env_vars(Apps);
-reconstruct_app_env_vars([]) ->
- ok.
-
-merge_app_vars(ConfigFile, {App, AppVars}) ->
- File = ensure_config_extension(ConfigFile),
- FileAppVars = app_vars_from_config_file(File, App),
- Dict1 = dict:from_list(AppVars),
- Dict2 = dict:from_list(FileAppVars),
- Dict3 = dict:merge(fun(_Key, _Value1, Value2) -> Value2 end, Dict1, Dict2),
- {App, dict:to_list(Dict3)}.
-
-ensure_config_extension(File) ->
- %% config files must end with .config on disk but when specifying them
- %% via the -config option the extension is optional
- BaseFileName = filename:basename(File, ".config"),
- DirName = filename:dirname(File),
- filename:join(DirName, BaseFileName ++ ".config").
-
-app_vars_from_config_file(File, App) ->
- case file:consult(File) of
- {ok, [Env]} ->
- proplists:get_value(App, Env, []);
- _ ->
- []
- end.
-
-wait_until_dead(Pid) when is_pid(Pid) ->
- Ref = erlang:monitor(process, Pid),
- receive
- {'DOWN', Ref, process, _Obj, Info} ->
- Info
- after 10*1000 ->
- exit({timeout_waiting_for, Pid})
- end;
-wait_until_dead(_) ->
- ok.
-
-pause_until_net_kernel_stopped() ->
- pause_until_net_kernel_stopped(10).
-
-pause_until_net_kernel_stopped(0) ->
- exit(net_kernel_stop_failed);
-pause_until_net_kernel_stopped(N) ->
- case node() of
- 'nonode@nohost' ->
- ?DEBUG("Stopped net kernel.\n", []),
- ok;
- _ ->
- timer:sleep(100),
- pause_until_net_kernel_stopped(N - 1)
- end.
diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl
index 2845b49..2cad9f2 100644
--- a/src/rebar_fetch.erl
+++ b/src/rebar_fetch.erl
@@ -45,13 +45,14 @@ current_ref(AppDir, {git, _, _}) ->
download_source(AppDir, Source) ->
TmpDir = ec_file:insecure_mkdtemp(),
+ AppDir1 = ec_cnv:to_list(AppDir),
case download_source_tmp(TmpDir, Source) of
{ok, _} ->
- ec_file:mkdir_p(AppDir),
- ok = ec_file:copy(TmpDir, binary_to_list(filename:absname(AppDir)), [recursive]);
+ ec_file:mkdir_p(AppDir1),
+ ok = ec_file:copy(TmpDir, filename:absname(AppDir1), [recursive]);
{tarball, File} ->
ok = erl_tar:extract(File, [{cwd,
- (filename:dirname(filename:absname(binary_to_list(AppDir))))}
+ (filename:dirname(filename:absname(AppDir1)))}
,compressed])
end.
diff --git a/src/rebar_getopt.erl b/src/rebar_getopt.erl
deleted file mode 100644
index 79b871d..0000000
--- a/src/rebar_getopt.erl
+++ /dev/null
@@ -1,914 +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(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_otp_app.erl b/src/rebar_otp_app.erl
index 3d946e9..14a01d7 100644
--- a/src/rebar_otp_app.erl
+++ b/src/rebar_otp_app.erl
@@ -42,7 +42,7 @@ compile(Config, App) ->
%% If we get an .app.src file, it needs to be pre-processed and
%% written out as a ebin/*.app file. That resulting file will then
%% be validated as usual.
- Dir = rebar_app_info:dir(App),
+ Dir = ec_cnv:to_list(rebar_app_info:dir(App)),
{Config2, App1} = case rebar_app_info:app_file_src(App) of
undefined ->
{Config, App};
diff --git a/src/rebar_prv_app_builder.erl b/src/rebar_prv_app_builder.erl
index 52a6b68..4c2d978 100644
--- a/src/rebar_prv_app_builder.erl
+++ b/src/rebar_prv_app_builder.erl
@@ -39,9 +39,8 @@ do(State) ->
{ok, State}.
build(State, AppInfo) ->
- ?INFO("Compiling ~s ~s~n", [rebar_app_info:name(AppInfo)
- ,rebar_app_info:original_vsn(AppInfo)]),
- rebar_erlc_compiler:compile(State, rebar_app_info:dir(AppInfo)),
+ ?INFO("Compiling ~s~n", [rebar_app_info:name(AppInfo)]),
+ rebar_erlc_compiler:compile(State, ec_cnv:to_list(rebar_app_info:dir(AppInfo))),
{ok, AppInfo1} = rebar_otp_app:compile(State, AppInfo),
AppInfo1.
diff --git a/src/rebar_escripter.erl b/src/rebar_prv_escripter.erl
index 2feb186..f153564 100644
--- a/src/rebar_escripter.erl
+++ b/src/rebar_prv_escripter.erl
@@ -24,7 +24,7 @@
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%% -------------------------------------------------------------------
--module(rebar_escripter).
+-module(rebar_prv_escripter).
-behaviour(rebar_provider).
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index 40df88c..3e13fc2 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -69,28 +69,43 @@ do(State) ->
{ok, State};
Deps ->
%% Split source deps form binary deps, needed to keep backwards compatibility
- {SrcDeps, Goals} = parse_deps(Deps),
- State1 = rebar_state:src_deps(rebar_state:goals(State, Goals), SrcDeps),
+ DepsDir = get_deps_dir(State),
+ {SrcDeps, Goals} = parse_deps(DepsDir, Deps),
+ State1 = rebar_state:src_deps(rebar_state:goals(State, Goals), lists:ukeysort(2, SrcDeps)),
State2 = update_src_deps(State1),
- Goals1 = rebar_state:goals(State2),
- {ok, Solved} = rlx_depsolver:solve(Graph, Goals1),
- Final = lists:map(fun({Name, Vsn}) ->
- FmtVsn = ec_cnv:to_binary(rlx_depsolver:format_version(Vsn)),
- {ok, P} = dict:find({Name, FmtVsn}, Packages),
- PkgDeps = proplists:get_value(<<"deps">>, P),
- Link = proplists:get_value(<<"link">>, P),
- Source = {Name, FmtVsn, Link},
- {ok, AppInfo} = rebar_app_info:new(Name, FmtVsn),
- AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps),
- rebar_app_info:source(AppInfo1, Source)
- end, Solved),
- ProjectApps = lists:map(fun(X) ->
- rebar_app_info:deps(X, [rebar_app_info:name(Y) || Y <- SrcDeps] ++ [element(1, G) || G <- Goals])
- end, rebar_state:apps_to_build(State2)),
- FinalDeps = ProjectApps ++ rebar_state:src_deps(State2) ++ Final,
- {ok, Sort} = rebar_topo:sort_apps(FinalDeps),
- [io:format("Build ~p~n", [rebar_app_info:name(A)]) || A <- Sort],
- {ok, State2}
+ case rebar_state:goals(State2) of
+ [] ->
+ ProjectApps = lists:map(fun(X) ->
+ rebar_app_info:deps(X, [rebar_app_info:name(Y) || Y <- SrcDeps])
+ end, rebar_state:apps_to_build(State2)),
+ FinalDeps = ProjectApps ++ rebar_state:src_deps(State2),
+ {ok, Sort} = rebar_topo:sort_apps(FinalDeps),
+ State3 = rebar_state:apps_to_build(State2, Sort),
+ {ok, State3};
+ Goals1 ->
+ {ok, Solved} = rlx_depsolver:solve(Graph, Goals1),
+ Final = lists:map(fun({Name, Vsn}) ->
+ FmtVsn = ec_cnv:to_binary(rlx_depsolver:format_version(Vsn)),
+ {ok, P} = dict:find({Name, FmtVsn}, Packages),
+ PkgDeps = proplists:get_value(<<"deps">>, P),
+ Link = proplists:get_value(<<"link">>, P),
+ Source = {Name, FmtVsn, Link},
+ {ok, AppInfo} = rebar_app_info:new(Name, FmtVsn),
+ AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps),
+ AppInfo2 =
+ rebar_app_info:dir(AppInfo1, get_deps_dir(DepsDir, <<Name/binary, "-", FmtVsn/binary>>)),
+ AppInfo3 = rebar_app_info:source(AppInfo2, Source),
+ ok = maybe_fetch(AppInfo3),
+ AppInfo3
+ end, Solved),
+ ProjectApps = lists:map(fun(X) ->
+ rebar_app_info:deps(X, [rebar_app_info:name(Y) || Y <- SrcDeps] ++ [element(1, G) || G <- Goals1])
+ end, rebar_state:apps_to_build(State2)),
+ FinalDeps = ProjectApps ++ rebar_state:src_deps(State2) ++ Final,
+ {ok, Sort} = rebar_topo:sort_apps(FinalDeps),
+ State3 = rebar_state:apps_to_build(State2, Sort),
+ {ok, State3}
+ end
end;
_Locks ->
{ok, State}
@@ -126,32 +141,40 @@ get_deps_dir(DepsDir, App) ->
%% Internal functions
%% ===================================================================
-%% Fetch missing binary deps
-update_deps(State) ->
- State.
-
update_src_deps(State) ->
SrcDeps = rebar_state:src_deps(State),
- case lists:foldl(fun(AppInfo, {StateAcc, SrcDepsAcc, GoalsAcc}) ->
- ok = maybe_fetch(StateAcc, AppInfo),
- C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
- S = rebar_state:new(rebar_state:new(), C, rebar_app_info:dir(AppInfo)),
- Deps = rebar_state:get(S, deps, []),
- {SrcDeps1, Goals} = parse_deps(Deps),
- NewSrcDeps = SrcDeps1++SrcDepsAcc,
- {StateAcc, NewSrcDeps, Goals++GoalsAcc}
- end, {State, [], rebar_state:goals(State)}, SrcDeps) of
- {State1, [], NewGoals} ->
- rebar_state:goals(State1, NewGoals);
- {State1, NewSrcDeps, NewGoals}->
- State2 = rebar_state:src_deps(rebar_state:goals(State1, NewGoals), SrcDeps++NewSrcDeps),
- update_src_deps(State2)
+ DepsDir = get_deps_dir(State),
+ case lists:foldl(fun(AppInfo, {SrcDepsAcc, GoalsAcc}) ->
+ ok = maybe_fetch(AppInfo),
+ {NewSrcDeps, NewGoals} = handle_dep(DepsDir, AppInfo),
+ {lists:ukeymerge(2, SrcDepsAcc, lists:ukeysort(2, NewSrcDeps)), NewGoals++GoalsAcc}
+ end, {SrcDeps, rebar_state:goals(State)}, SrcDeps) of
+ {SrcDeps, NewGoals} ->
+ rebar_state:goals(State, NewGoals);
+ {NewSrcDeps, NewGoals} ->
+ io:format("NEWSRC ~p~n", [NewSrcDeps]),
+ State1 = rebar_state:src_deps(rebar_state:goals(State, NewGoals), NewSrcDeps),
+ update_src_deps(State1)
end.
-maybe_fetch(_State, _Dep) ->
- ok.
+handle_dep(DepsDir, AppInfo) ->
+ C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
+ S = rebar_state:new(rebar_state:new(), C, rebar_app_info:dir(AppInfo)),
+ Deps = rebar_state:get(S, deps, []),
+ parse_deps(DepsDir, Deps).
+
+maybe_fetch(AppInfo) ->
+ AppDir = rebar_app_info:dir(AppInfo),
+ case filelib:is_dir(AppDir) of
+ false ->
+ ?INFO("Fetching ~s~n", [rebar_app_info:name(AppInfo)]),
+ Source = rebar_app_info:source(AppInfo),
+ rebar_fetch:download_source(AppDir, Source);
+ true ->
+ ok
+ end.
-parse_deps(Deps) ->
+parse_deps(DepsDir, Deps) ->
lists:foldl(fun({Name, Vsn}, {SrcDepsAcc, GoalsAcc}) ->
{SrcDepsAcc, [parse_goal(ec_cnv:to_binary(Name)
,ec_cnv:to_binary(Vsn)) | GoalsAcc]};
@@ -159,7 +182,8 @@ parse_deps(Deps) ->
{SrcDepsAcc, [ec_cnv:to_binary(Name) | GoalsAcc]};
({Name, _, Source}, {SrcDepsAcc, GoalsAcc}) ->
{ok, Dep} = rebar_app_info:new(Name),
- Dep1 = rebar_app_info:source(Dep, Source),
+ Dep1 = rebar_app_info:source(
+ rebar_app_info:dir(Dep, get_deps_dir(DepsDir, Name)), Source),
{[Dep1 | SrcDepsAcc], GoalsAcc}
end, {[], []}, Deps).
diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl
deleted file mode 100644
index af805c8..0000000
--- a/src/rebar_require_vsn.erl
+++ /dev/null
@@ -1,122 +0,0 @@
-%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 et
-%% -------------------------------------------------------------------
-%%
-%% rebar: Erlang Build Tools
-%%
-%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a copy
-%% of this software and associated documentation files (the "Software"), to deal
-%% in the Software without restriction, including without limitation the rights
-%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-%% copies of the Software, and to permit persons to whom the Software is
-%% furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-%% THE SOFTWARE.
-%%
-%% -------------------------------------------------------------------
-
--module(rebar_require_vsn).
-
--include("rebar.hrl").
-
--export([compile/2,
- eunit/2]).
-
-%% for internal use only
--export([info/2,
- version_tuple/2]).
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
-compile(Config, _) ->
- check_versions(Config).
-
-eunit(Config, _) ->
- check_versions(Config).
-
-%% ====================================================================
-%% Internal functions
-%% ====================================================================
-
-info(help, compile) ->
- info_help();
-info(help, eunit) ->
- info_help().
-
-info_help() ->
- ?CONSOLE(
- "Check required ERTS or OTP release version.~n"
- "~n"
- "Valid rebar.config options:~n"
- " ~p~n"
- " ~p~n"
- " ~p~n",
- [
- {require_erts_vsn, ".*"},
- {require_otp_vsn, ".*"},
- {require_min_otp_vsn, ".*"}
- ]).
-
-check_versions(Config) ->
- ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"),
- ReOpts = [{capture, none}],
- case re:run(erlang:system_info(version), ErtsRegex, ReOpts) of
- match ->
- ?DEBUG("Matched required ERTS version: ~s -> ~s\n",
- [erlang:system_info(version), ErtsRegex]);
- nomatch ->
- ?ABORT("ERTS version ~s does not match required regex ~s\n",
- [erlang:system_info(version), ErtsRegex])
- end,
-
- OtpRegex = rebar_config:get(Config, require_otp_vsn, ".*"),
- case re:run(erlang:system_info(otp_release), OtpRegex, ReOpts) of
- match ->
- ?DEBUG("Matched required OTP release: ~s -> ~s\n",
- [erlang:system_info(otp_release), OtpRegex]);
- nomatch ->
- ?ABORT("OTP release ~s does not match required regex ~s\n",
- [erlang:system_info(otp_release), OtpRegex])
- end,
-
- case rebar_config:get(Config, require_min_otp_vsn, undefined) of
- undefined -> ?DEBUG("Min OTP version unconfigured~n", []);
- MinOtpVsn ->
- {MinMaj, MinMin} = version_tuple(MinOtpVsn, "configured"),
- {OtpMaj, OtpMin} = version_tuple(erlang:system_info(otp_release),
- "OTP Release"),
- case {OtpMaj, OtpMin} >= {MinMaj, MinMin} of
- true ->
- ?DEBUG("~s satisfies the requirement for vsn ~s~n",
- [erlang:system_info(otp_release),
- MinOtpVsn]);
- false ->
- ?ABORT("OTP release ~s or later is required, you have: ~s~n",
- [MinOtpVsn,
- erlang:system_info(otp_release)])
- end
- end.
-
-version_tuple(OtpRelease, Type) ->
- case re:run(OtpRelease, "R?(\\d+)B?-?(\\d+)?", [{capture, all, list}]) of
- {match, [_Full, Maj, Min]} ->
- {list_to_integer(Maj), list_to_integer(Min)};
- {match, [_Full, Maj]} ->
- {list_to_integer(Maj), 0};
- nomatch ->
- ?ABORT("Cannot parse ~s version string: ~s~n",
- [Type, OtpRelease])
- end.