From af5096b6f071118ea680f3b03d2ab90a95e1a334 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 7 Dec 2009 10:07:01 -0700 Subject: [PATCH 1/8] Make sure to bail w/ return code of 1 when error has occurred --- src/rebar.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rebar.erl b/src/rebar.erl index fe1821b..0e92731 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -27,4 +27,9 @@ -export([main/1]). main(Args) -> - rebar_core:run(Args). + case catch(rebar_core:run(Args)) of + ok -> + ok; + _ -> + halt(1) + end. From cb1899b818fded4ddbeadfa8ab10efae1030f24d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 7 Dec 2009 15:17:03 -0700 Subject: [PATCH 2/8] Adding convenience method for determing verbosity of run --- src/rebar_config.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 199461c..5684e66 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -28,7 +28,8 @@ get_modules/2, get_list/3, get/3, - set_global/2, get_global/2]). + set_global/2, get_global/2, + is_verbose/0]). -include("rebar.hrl"). @@ -91,6 +92,14 @@ get_global(Key, Default) -> Value end. +is_verbose() -> + case get_global(verbose, "0") of + "1" -> + true; + _ -> + false + end. + %% =================================================================== %% Internal functions From 95d52f860df1be7d2df5d7fce0c499ec4832add9 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 7 Dec 2009 15:17:24 -0700 Subject: [PATCH 3/8] Refactoring eunit to support in-module tests --- src/rebar_eunit.erl | 129 ++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 82 deletions(-) diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index 989d356..d63ff92 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -24,7 +24,8 @@ %% ------------------------------------------------------------------- %% %% Targets: -%% eunit - runs eunit tests (with suffix _tests.erl) in ./test +%% eunit - runs eunit tests +%% clean - remove .eunit directory %% %% Global options: %% verbose=1 - show extra output from the eunit test @@ -38,67 +39,61 @@ -include("rebar.hrl"). +-define(EUNIT_DIR, ".eunit"). + %% =================================================================== %% Public API %% =================================================================== eunit(Config, File) -> - run_test_if_present("test", Config, File). + %% Make sure ?EUNIT_DIR/ directory exists (tack on dummy module) + ok = filelib:ensure_dir(?EUNIT_DIR ++ "/foo"), + + %% Compile all erlang from src/ into ?EUNIT_DIR + rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam", fun compile_erl/2, + rebar_config:get_list(Config, erl_first_files, [])), + + %% TODO: If there are other wildcards specified in eunit_sources, compile them + + %% Save current code path and then prefix ?EUNIT_DIR on it so that our modules + %% are found there + InitCodePath = code:get_path(), + true = code:add_patha(?EUNIT_DIR), + + %% Enable verbose in eunit if so requested.. + case rebar_config:is_verbose() of + true -> + BaseOpts = [verbose]; + false -> + BaseOpts = [] + end, + + %% Run eunit + EunitOpts = BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []), + case catch(eunit:test({dir, ?EUNIT_DIR}, EunitOpts)) of + ok -> + ok; + _ -> + ?CONSOLE("One or more eunit tests failed.\n", []), + ?FAIL + end, + + %% Restore code path + true = code:set_path(InitCodePath), + ok. + +clean(Config, File) -> + rebar_file_utils:rm_rf(?EUNIT_DIR). + %% =================================================================== %% Internal functions %% =================================================================== -run_test_if_present(TestDir, Config, File) -> - case filelib:is_dir(TestDir) of - false -> - ?WARN("~s directory not present - skipping\n", [TestDir]), - ok; - true -> - run_test(TestDir, Config, File) - end. -run_test(TestDir, Config, _File) -> - case rebar_erlc_compiler:do_compile(Config, "test/*.erl", "test", ".erl", ".beam", - fun compile_test/2, []) of - ok -> - Cwd = rebar_utils:get_cwd(), - CodePath = code:get_path(), - true = code:add_patha(filename:join(Cwd, "test")), - - case rebar_config:get_global(verbose, "0") of - "0" -> - Verbose = []; - _ -> - Verbose = [verbose] - end, - - Tests = find_tests(TestDir), - Opts = Verbose, - ?INFO("Running tests: ~p\n", [Tests]), - - change_to_work_dir(Cwd), - - case catch eunit:test(Tests, Opts) of - ok -> - ok; - _ -> - ?CONSOLE("One or more tests failed\n", []), - ?FAIL - end, - - code:set_path(CodePath), - file:set_cwd(Cwd), - ok; - - _Other -> - ?ERROR("Compiling eunit tests failed\n",[]), - ?FAIL - end. - - -compile_test(Source, Config) -> - Opts = [{i, "include"}, {outdir, "test"}, debug_info, report] ++ - rebar_erlc_compiler:compile_opts(Config, erl_opts), +compile_erl(Source, Config) -> + ErlOpts = rebar_config:get_list(Config, erl_opts, []), + EunitOpts = rebar_config:get_list(Config, eunit_compile_opts, []), + Opts = [{i, "include"}, {outdir, ?EUNIT_DIR}, {d, 'TEST'}, debug_info, report] ++ ErlOpts ++ EunitOpts, case compile:file(Source, Opts) of {ok, _} -> ok; @@ -107,35 +102,5 @@ compile_test(Source, Config) -> end. -find_tests(TestDir) -> - case rebar_config:get_global(suite, undefined) of - undefined -> - {ok, Files} = file:list_dir(TestDir), - Filter = fun(Filename) -> - lists:suffix("_tests.erl", Filename) - end, - Erls = lists:filter(Filter, Files), - [list_to_atom(filename:basename(F, ".erl")) || F <- Erls]; - Suite -> - %% Add the _tests suffix if missing - case lists:suffix("_tests", Suite) of - true -> - [list_to_atom(Suite)]; - false -> - [list_to_atom(Suite ++ "_tests")] - end - end. - -%% Clear the contents of the work dir and change to it -%% -change_to_work_dir(MainDir) -> - WorkDir = filename:join([MainDir, "logs", "eunit_work"]), - rebar_file_utils:rm_rf(WorkDir), - ok = filelib:ensure_dir(filename:join(WorkDir, "x")), - ok = file:set_cwd(WorkDir). - - - - From ffa0cda467ce851ff57aa23b92ab8f3fd679fb33 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 7 Dec 2009 16:03:56 -0700 Subject: [PATCH 4/8] Updating bootstrap to embed the build time into a macro that we can pull via "version" command --- bootstrap | 20 ++++++++++++++++++-- src/rebar_core.erl | 13 ++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/bootstrap b/bootstrap index 237c5b2..37145aa 100755 --- a/bootstrap +++ b/bootstrap @@ -2,8 +2,21 @@ %% -*- erlang -*- main(Args) -> + %% Get a string repr of build time + Built = build_time(), + + %% Check for force=1 flag to force a rebuild + case lists:member("force=1", Args) of + true -> + [] = os:cmd("rm -rf ebin/*.beam"), + ok; + false -> + ok + end, + %% Compile all src/*.erl to ebin - case make:files(filelib:wildcard("src/*.erl"), [{outdir, "ebin"}, {i, "include"}]) of + case make:files(filelib:wildcard("src/*.erl"), [{outdir, "ebin"}, {i, "include"}, + {d, 'BUILD_TIME', Built}]) of up_to_date -> ok; error -> @@ -55,5 +68,8 @@ main(Args) -> "and you can use rebar to build OTP-compliant apps.\n"). - +build_time() -> + {{Y, M, D}, {H, Min, S}} = calendar:now_to_universal_time(now()), + lists:flatten(io_lib:format("~4..0w~2..0w~2..0w_~2..0w~2..0w~2..0w", [Y, M, D, H, Min, S])). + diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 7a40200..d39a425 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -28,11 +28,22 @@ -include("rebar.hrl"). +-ifndef(BUILD_TIME). +-define(BUILD_TIME, "undefined"). +-endif. + %% =================================================================== %% Public API %% =================================================================== -run(Args) -> +run(["version"]) -> + %% Load application spec and display vsn and build time info + ok = application:load(rebar), + {ok, Vsn} = application:get_key(rebar, vsn), + ?CONSOLE("Version ~s built ~s\n", [Vsn, ?BUILD_TIME]), + ok; +run(Args) -> + ?CONSOLE("Args: ~p\n", [Args]), %% Filter all the flags (i.e. string of form key=value) from the %% command line arguments. What's left will be the commands to run. Commands = filter_flags(Args, []), From b088139ed09c16816155eb79560b64878ebc2ab6 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 8 Dec 2009 15:52:53 -0700 Subject: [PATCH 5/8] Remove unnecessary debug in _core; adding EQC flag for eunit builds --- src/rebar_core.erl | 1 - src/rebar_eunit.erl | 26 ++++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/rebar_core.erl b/src/rebar_core.erl index d39a425..61e9dee 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -43,7 +43,6 @@ run(["version"]) -> ?CONSOLE("Version ~s built ~s\n", [Vsn, ?BUILD_TIME]), ok; run(Args) -> - ?CONSOLE("Args: ~p\n", [Args]), %% Filter all the flags (i.e. string of form key=value) from the %% command line arguments. What's left will be the commands to run. Commands = filter_flags(Args, []), diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index d63ff92..69f8a0a 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -91,9 +91,17 @@ clean(Config, File) -> %% =================================================================== compile_erl(Source, Config) -> + case is_quickcheck_avail() of + true -> + EqcOpts = [{d, 'EQC'}]; + false -> + EqcOpts = [] + end, + ErlOpts = rebar_config:get_list(Config, erl_opts, []), EunitOpts = rebar_config:get_list(Config, eunit_compile_opts, []), - Opts = [{i, "include"}, {outdir, ?EUNIT_DIR}, {d, 'TEST'}, debug_info, report] ++ ErlOpts ++ EunitOpts, + Opts = [{i, "include"}, {outdir, ?EUNIT_DIR}, {d, 'TEST'}, debug_info, report] ++ + ErlOpts ++ EunitOpts ++ EqcOpts, case compile:file(Source, Opts) of {ok, _} -> ok; @@ -101,6 +109,20 @@ compile_erl(Source, Config) -> ?FAIL end. - +is_quickcheck_avail() -> + case erlang:get(is_quickcheck_avail) of + undefined -> + case code:lib_dir(eqc, include) of + {error, bad_name} -> + IsAvail = false; + Dir -> + IsAvail = filelib:is_file(filename:join(Dir, "eqc.hrl")) + end, + erlang:put(is_quickcheck_avail, IsAvail), + ?DEBUG("Quickcheck availability: ~p\n", [IsAvail]), + IsAvail; + IsAvail -> + IsAvail + end. From d894dc54489a2891e00a27d2ddf63690757d93ef Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Dec 2009 21:59:58 -0700 Subject: [PATCH 6/8] Make sure crypto is running as lots of things use it --- src/rebar_core.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 61e9dee..fbc5c61 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -50,6 +50,9 @@ run(Args) -> %% Pre-load the rebar app so that we get default configuration ok = application:load(rebar), + %% Make sure crypto is running + crypto:start(), + %% Initialize logging system rebar_log:init(), From 588337e78bfd093a2003929c2bbc1d824a08fc76 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 9 Dec 2009 22:00:28 -0700 Subject: [PATCH 7/8] Add support for coverage generation --- src/rebar_eunit.erl | 105 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index 69f8a0a..a75a8a0 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -50,9 +50,18 @@ eunit(Config, File) -> ok = filelib:ensure_dir(?EUNIT_DIR ++ "/foo"), %% Compile all erlang from src/ into ?EUNIT_DIR - rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam", fun compile_erl/2, + rebar_erlc_compiler:do_compile(Config, "src/*.erl", ?EUNIT_DIR, ".erl", ".beam", + fun compile_erl/2, rebar_config:get_list(Config, erl_first_files, [])), + %% 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. So, + %% we do it by hand. :( + Modules = [list_to_atom(filename:basename(N, ".beam")) || + N <- filelib:wildcard("*.beam", "ebin")], + %% TODO: If there are other wildcards specified in eunit_sources, compile them %% Save current code path and then prefix ?EUNIT_DIR on it so that our modules @@ -68,9 +77,22 @@ eunit(Config, File) -> BaseOpts = [] end, + %% If cover support is requested, set it up + case rebar_config:get(Config, cover_enabled, false) of + true -> + cover_init(Config); + _ -> + ok + end, + %% Run eunit EunitOpts = BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []), - case catch(eunit:test({dir, ?EUNIT_DIR}, EunitOpts)) of + EunitResult = (catch eunit:test(Modules, EunitOpts)), + + %% Analyze cover modules + cover_analyze(Config, cover:modules()), + + case EunitResult of ok -> ok; _ -> @@ -78,6 +100,7 @@ eunit(Config, File) -> ?FAIL end, + %% Restore code path true = code:set_path(InitCodePath), ok. @@ -125,4 +148,82 @@ is_quickcheck_avail() -> IsAvail end. +cover_init(Config) -> + %% Make sure any previous runs of cover don't unduly influence + cover:reset(), + ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]), + + case cover:compile_beam_directory(?EUNIT_DIR) of + {error, Reason2} -> + ?ERROR("Cover compilation failed: ~p\n", [Reason2]), + ?FAIL; + Modules -> + %% It's not an error for cover compilation to fail partially, but we do want + %% to warn about them + [?CONSOLE("Cover compilation warning: ~p", [Desc]) || {error, Desc} <- Modules], + + %% Identify the modules that were compiled successfully + case [ M || {ok, M} <- Modules] of + [] -> + %% No modules compiled successfully...fail + ?ERROR("Cover failed to compile any modules; aborting.\n", []), + ?FAIL; + _ -> + %% At least one module compiled successfully + ok + end + end. + +cover_analyze(Config, []) -> + ok; +cover_analyze(Config, Modules) -> + %% Generate coverage info for all the cover-compiled modules + Coverage = [cover_analyze_mod(M) || M <- Modules], + + %% Write index of coverage info + cover_write_index(lists:sort(Coverage)), + + %% Write coverage details for each file + [{ok, _} = cover:analyze_to_file(M, cover_file(M), [html]) || {M, _, _} <- Coverage], + + Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]), + ?CONSOLE("Cover analysis: ~s\n", [Index]). + + +cover_analyze_mod(Module) -> + case cover:analyze(Module, coverage, module) of + {ok, {Module, {Covered, NotCovered}}} -> + {Module, Covered, NotCovered}; + {error, Reason} -> + ?ERROR("Cover analyze failed for ~p: ~p ~p\n", + [Module, Reason, code:which(Module)]), + {0,0} + end. + +cover_write_index(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, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]), + ok = file:write(F, "Coverage Summary\n" + "

Coverage Summary

\n"), + ok = file:write(F, ?FMT("

Total: ~w%

\n", [TotalCoverage])), + ok = file:write(F, "\n"), + + [ok = file:write(F, ?FMT("\n", + [Module, Module, percentage(Cov, NotCov)])) || + {Module, Cov, NotCov} <- Coverage], + ok = file:write(F, "
ModuleCoverage %
~s~w%
"), + file:close(F). + +cover_file(Module) -> + filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]). + + +percentage(Cov, NotCov) -> + trunc((Cov / (Cov + NotCov)) * 100). From 849078026e50816f6303e3a73fff1dd8c0206896 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 10 Dec 2009 12:16:15 -0700 Subject: [PATCH 8/8] Add ability to forcibly overwrite existing release --- src/rebar_reltool.erl | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl index d228202..e1ffbe0 100644 --- a/src/rebar_reltool.erl +++ b/src/rebar_reltool.erl @@ -137,7 +137,20 @@ run_reltool(Config, ReltoolConfig) -> {ok, Spec} -> dump_spec(Spec), TargetDir = target_dir(Config, ReltoolConfig), - ok = file:make_dir(TargetDir), + case file:make_dir(TargetDir) of + ok -> + ok; + {error, eexist} -> + %% Output directory already exists; if force=1, wipe it out + case rebar_config:get_global(force, "0") of + "1" -> + rebar_file_utils:rm_rf(TargetDir), + ok = file:make_dir(TargetDir); + _ -> + ?ERROR("Release target directory ~p already exists!\n", [TargetDir]), + ?FAIL + end + end, case reltool:eval_target_spec(Spec, code:root_dir(), TargetDir) of ok -> ok;