diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rebar.erl | 10 | ||||
-rw-r--r-- | src/rebar_app_info.erl | 18 | ||||
-rw-r--r-- | src/rebar_cleaner.erl | 56 | ||||
-rw-r--r-- | src/rebar_cover_utils.erl | 261 | ||||
-rw-r--r-- | src/rebar_ct.erl | 388 | ||||
-rw-r--r-- | src/rebar_edoc.erl | 130 | ||||
-rw-r--r-- | src/rebar_eunit.erl | 675 | ||||
-rw-r--r-- | src/rebar_fetch.erl | 7 | ||||
-rw-r--r-- | src/rebar_getopt.erl | 914 | ||||
-rw-r--r-- | src/rebar_otp_app.erl | 2 | ||||
-rw-r--r-- | src/rebar_prv_app_builder.erl | 5 | ||||
-rw-r--r-- | src/rebar_prv_escripter.erl (renamed from src/rebar_escripter.erl) | 2 | ||||
-rw-r--r-- | src/rebar_prv_install_deps.erl | 110 | ||||
-rw-r--r-- | src/rebar_require_vsn.erl | 122 |
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 -%% "'$(<app_name>)'" -%% '"."' '[<options>]')</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. |