diff --git a/inttest/require_vsn/rebar.config b/inttest/require_vsn/rebar.config new file mode 100644 index 0000000..1311ffa --- /dev/null +++ b/inttest/require_vsn/rebar.config @@ -0,0 +1,3 @@ +{require_erts_vsn, "no_such_erts_vsn-1.2"}. +{require_otp_vsn, "no_such_otp_vsn-1.2"}. +{require_min_otp_vsn, "no_such_min_otp_vsn-1.0"}. diff --git a/inttest/require_vsn/require_vsn_rt.erl b/inttest/require_vsn/require_vsn_rt.erl new file mode 100644 index 0000000..fc720b5 --- /dev/null +++ b/inttest/require_vsn/require_vsn_rt.erl @@ -0,0 +1,115 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% 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(require_vsn_rt). +-export([files/0, + run/1]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {create, "ebin/require_vsn.app", app(require_vsn, [])} + ]. + +run(_Dir) -> + SharedExpected = "==> require_vsn_rt \\(compile\\)", + %% Provoke ABORT due to failed require vsn check. + retest:log(info, "Check require vsn failure~n"), + ok = check_output("./rebar compile", should_fail, + [SharedExpected, "ERROR: "], + ["WARN: "]), + %% Treat version constraints as warnings. + retest:log(info, "Check require vsn success with -k/--keep-going~n"), + ok = check_output("./rebar -k compile", should_succeed, + [SharedExpected, "WARN: "], + ["ERROR: "]), + ok. + +check_output(Cmd, FailureMode, Expected, Unexpected) -> + case {retest:sh(Cmd), FailureMode} of + {{error, _}=Error, should_succeed} -> + retest:log(error, "cmd '~s' failed:~n~p~n", [Cmd, Error]), + Error; + {{ok, Captured}, should_succeed} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected); + {{error, {stopped, {_Rc, Captured}}}, should_fail} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected) + end. + +check_output1(Cmd, Captured, Expected, Unexpected) -> + ReOpts = [{capture, all, list}], + ExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + retest:log(error, + "Expected pattern '~s' missing " + "in the following output:~n" + "=== BEGIN ===~n~s~n=== END ===~n", + [Pattern, Captured]), + {true, Pattern}; + {match, _} -> + false + end + end, Expected), + + UnExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + false; + {match, [Match]} -> + retest:log( + console, + "Unexpected output when running cmd '~s':~n~s~n", + [Cmd, Match]), + {true, Match} + end + end, Unexpected), + + case {ExMatches, UnExMatches} of + {[], []} -> + ok; + _ -> + error + end. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl index af805c8..b01723a 100644 --- a/src/rebar_require_vsn.erl +++ b/src/rebar_require_vsn.erl @@ -35,7 +35,7 @@ %% for internal use only -export([info/2, - version_tuple/2]). + version_tuple/3]). %% =================================================================== %% Public API @@ -71,6 +71,10 @@ info_help() -> ]). check_versions(Config) -> + ShouldAbort = case rebar_config:get_xconf(Config, keep_going, false) of + true -> keep_going; + false -> abort + end, ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"), ReOpts = [{capture, none}], case re:run(erlang:system_info(version), ErtsRegex, ReOpts) of @@ -78,8 +82,10 @@ check_versions(Config) -> ?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]) + maybe_abort( + ShouldAbort, + "ERTS version ~s does not match required regex ~s\n", + [erlang:system_info(version), ErtsRegex]) end, OtpRegex = rebar_config:get(Config, require_otp_vsn, ".*"), @@ -88,15 +94,19 @@ check_versions(Config) -> ?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]) + maybe_abort( + ShouldAbort, + "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), + {MinMaj, MinMin} = version_tuple(ShouldAbort, MinOtpVsn, + "configured"), + {OtpMaj, OtpMin} = version_tuple(ShouldAbort, + erlang:system_info(otp_release), "OTP Release"), case {OtpMaj, OtpMin} >= {MinMaj, MinMin} of true -> @@ -104,19 +114,27 @@ check_versions(Config) -> [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)]) + maybe_abort( + ShouldAbort, + "OTP release ~s or later is required, you have: ~s~n", + [MinOtpVsn, + erlang:system_info(otp_release)]) end end. -version_tuple(OtpRelease, Type) -> +version_tuple(ShouldAbort, 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]) + maybe_abort(ShouldAbort, + "Cannot parse ~s version string: ~s~n", + [Type, OtpRelease]) end. + +maybe_abort(abort, Format, Data) -> + ?ABORT(Format, Data); +maybe_abort(keep_going, Format, Data) -> + ?WARN(Format, Data). diff --git a/test/rebar_require_vsn_tests.erl b/test/rebar_require_vsn_tests.erl index 2d3a1ec..ac0e85e 100644 --- a/test/rebar_require_vsn_tests.erl +++ b/test/rebar_require_vsn_tests.erl @@ -6,18 +6,21 @@ version_tuple_test_() -> [%% typical cases - ?_assert(rebar_require_vsn:version_tuple("R15B", "eunit") =:= {15, 0}), - ?_assert(rebar_require_vsn:version_tuple("R15B01", "eunit") =:= {15, 1}), - ?_assert(rebar_require_vsn:version_tuple("R15B02", "eunit") =:= {15, 2}), - ?_assert(rebar_require_vsn:version_tuple("R15B03-1", "eunit") =:= {15, 3}), - ?_assert(rebar_require_vsn:version_tuple("R15B03", "eunit") =:= {15, 3}), - ?_assert(rebar_require_vsn:version_tuple("R16B", "eunit") =:= {16, 0}), - ?_assert(rebar_require_vsn:version_tuple("R16B01", "eunit") =:= {16, 1}), - ?_assert(rebar_require_vsn:version_tuple("R16B02", "eunit") =:= {16, 2}), - ?_assert(rebar_require_vsn:version_tuple("R16B03", "eunit") =:= {16, 3}), - ?_assert(rebar_require_vsn:version_tuple("R16B03-1", "eunit") =:= {16, 3}), - ?_assert(rebar_require_vsn:version_tuple("17", "eunit") =:= {17, 0}), + ?_assert(check("R15B", "eunit") =:= {15, 0}), + ?_assert(check("R15B01", "eunit") =:= {15, 1}), + ?_assert(check("R15B02", "eunit") =:= {15, 2}), + ?_assert(check("R15B03-1", "eunit") =:= {15, 3}), + ?_assert(check("R15B03", "eunit") =:= {15, 3}), + ?_assert(check("R16B", "eunit") =:= {16, 0}), + ?_assert(check("R16B01", "eunit") =:= {16, 1}), + ?_assert(check("R16B02", "eunit") =:= {16, 2}), + ?_assert(check("R16B03", "eunit") =:= {16, 3}), + ?_assert(check("R16B03-1", "eunit") =:= {16, 3}), + ?_assert(check("17", "eunit") =:= {17, 0}), %% error cases - ?_assertException(throw, rebar_abort, rebar_require_vsn:version_tuple("", "eunit")), - ?_assertException(throw, rebar_abort, rebar_require_vsn:version_tuple("abc", "eunit")) + ?_assertException(throw, rebar_abort, check("", "eunit")), + ?_assertException(throw, rebar_abort, check("abc", "eunit")) ]. + +check(OtpRelease, Type) -> + rebar_require_vsn:version_tuple(abort, OtpRelease, Type).