summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml10
-rw-r--r--Makefile21
-rw-r--r--README.md74
-rw-r--r--THANKS20
-rwxr-xr-xbootstrap18
-rw-r--r--dialyzer_reference3
-rw-r--r--ebin/rebar.app7
-rw-r--r--include/rebar.hrl6
-rw-r--r--inttest/ct1/rebar.config1
-rw-r--r--inttest/ct2/ct2_rt.erl4
-rw-r--r--inttest/t_custom_config/custom.config1
-rw-r--r--inttest/t_custom_config/t_custom_config_rt.erl9
-rw-r--r--inttest/tdeps1/a.erl1
-rw-r--r--inttest/tdeps1/a.rebar.config1
-rw-r--r--inttest/tdeps1/b.hrl2
-rw-r--r--inttest/tdeps1/b.rebar.config1
-rw-r--r--inttest/tdeps1/c.hrl1
-rw-r--r--inttest/tdeps1/tdeps1_rt.erl3
-rw-r--r--inttest/tdeps2/a.erl1
-rw-r--r--inttest/tdeps2/a.rebar.config1
-rw-r--r--inttest/tdeps2/b.hrl2
-rw-r--r--inttest/tdeps2/b.rebar.config1
-rw-r--r--inttest/tdeps2/c.hrl1
-rw-r--r--inttest/tdeps2/tdeps2_rt.erl3
-rw-r--r--inttest/tplugins/tplugins_rt.erl2
-rw-r--r--priv/shell-completion/bash/rebar7
-rwxr-xr-xpriv/templates/simplenode.runner74
-rw-r--r--priv/templates/simplenode.windows.runner.cmd38
-rw-r--r--priv/templates/simplenode.windows.start_erl.cmd21
-rw-r--r--rebar.config5
-rw-r--r--rebar.config.sample39
-rw-r--r--rebar.config.script20
-rw-r--r--src/mustache.erl38
-rw-r--r--src/rebar.erl218
-rw-r--r--src/rebar_abnfc_compiler.erl4
-rw-r--r--src/rebar_app_utils.erl123
-rw-r--r--src/rebar_appups.erl13
-rw-r--r--src/rebar_asn1_compiler.erl6
-rw-r--r--src/rebar_base_compiler.erl47
-rw-r--r--src/rebar_config.erl171
-rw-r--r--src/rebar_core.erl266
-rw-r--r--src/rebar_ct.erl74
-rw-r--r--src/rebar_deps.erl386
-rw-r--r--src/rebar_edoc.erl73
-rw-r--r--src/rebar_erlc_compiler.erl236
-rw-r--r--src/rebar_erlydtl_compiler.erl6
-rw-r--r--src/rebar_escripter.erl77
-rw-r--r--src/rebar_eunit.erl419
-rw-r--r--src/rebar_file_utils.erl24
-rw-r--r--src/rebar_lfe_compiler.erl9
-rw-r--r--src/rebar_log.erl17
-rw-r--r--src/rebar_neotoma_compiler.erl4
-rw-r--r--src/rebar_otp_app.erl44
-rw-r--r--src/rebar_port_compiler.erl228
-rw-r--r--src/rebar_protobuffs_compiler.erl18
-rw-r--r--src/rebar_qc.erl167
-rw-r--r--src/rebar_rel_utils.erl56
-rw-r--r--src/rebar_reltool.erl95
-rw-r--r--src/rebar_shell.erl56
-rw-r--r--src/rebar_subdirs.erl2
-rw-r--r--src/rebar_templater.erl242
-rw-r--r--src/rebar_upgrade.erl25
-rw-r--r--src/rebar_utils.erl215
-rw-r--r--src/rebar_xref.erl24
-rw-r--r--test/rebar_eunit_tests.erl206
66 files changed, 2487 insertions, 1506 deletions
diff --git a/.gitignore b/.gitignore
index 8852dc9..ef672fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,8 @@ rebar
.*.swp
rt.work
.hgignore
-.eunit
+.test
dialyzer_warnings
-xref_warnings
rebar.cmd
-rebar.ps1
+.eunit
+deps
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fc9c771
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: erlang
+notifications:
+ webhooks: http://basho-engbot.herokuapp.com/travis?key=a0e66154af272adba328195454cfdc10ff5f33f5
+ email: eng@basho.com
+otp_release:
+ - R15B01
+ - R15B
+ - R14B04
+ - R14B03
+script: "make debug xref clean all deps test"
diff --git a/Makefile b/Makefile
index 1fda878..3f7b8ee 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,18 @@
-.PHONY: dialyzer_warnings xref_warnings
+.PHONY: clean dialyzer_warnings xref_warnings deps test
+
+REBAR=$(PWD)/rebar
+RETEST=$(PWD)/deps/retest/retest
all:
./bootstrap
clean:
- @rm -rf rebar ebin/*.beam inttest/rt.work
+ @rm -rf rebar ebin/*.beam inttest/rt.work rt.work .test
debug:
@./bootstrap debug
-check: debug xref dialyzer
+check: debug xref dialyzer deps test
xref:
@./rebar xref
@@ -23,5 +26,13 @@ dialyzer_warnings:
binary: VSN = $(shell ./rebar -V)
binary: clean all
- cp rebar ../rebar.wiki/rebar
- (cd ../rebar.wiki && git commit -m "Update $(VSN)" rebar) \ No newline at end of file
+ @cp rebar ../rebar.wiki/rebar
+ (cd ../rebar.wiki && git commit -m "Update $(VSN)" rebar)
+
+deps:
+ @REBAR_EXTRA_DEPS=1 ./rebar get-deps
+ @(cd deps/retest && $(REBAR) compile escriptize)
+
+test:
+ @$(REBAR) eunit
+ @$(RETEST) inttest
diff --git a/README.md b/README.md
index 443475e..68236e1 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ rebar
rebar is an Erlang build tool that makes it easy to compile and
test Erlang applications, port drivers and releases.
+[![Build Status](https://secure.travis-ci.org/basho/rebar.png?branch=master)](http://travis-ci.org/basho/rebar)
+
rebar is a self-contained Erlang script, so it's easy to distribute or even
embed directly in a project. Where possible, rebar uses standard Erlang/OTP
conventions for project structures, thus minimizing the amount of build
@@ -71,8 +73,8 @@ Do not mix spaces and tabs.
Do not introduce lines longer than 80 characters.
[erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) indentation is preferred.
-vi-only users are encouraged to give [Vim emulation](http://emacswiki.org/emacs/Evil)
-([more info](https://gitorious.org/evil/pages/Home)) a try.
+vi-only users are encouraged to
+give [Vim emulation](http://emacswiki.org/emacs/Evil) ([more info](https://gitorious.org/evil/pages/Home)) a try.
Writing Commit Messages
-----------------------
@@ -106,42 +108,46 @@ Longer description (wrap at 72 characters)
* Break up logical changes
* Make whitespace changes separately
-Dialyzer and Tidier
--------------------
+Run checks
+----------
-Before you submit a patch check for
+Before you submit a patch, run ``make check`` to execute
+the test suite and check for
[xref](http://www.erlang.org/doc/man/xref.html) and
[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html)
-warnings.
-
-A successful run of ``make check`` looks like:
+warnings. You may have to run ``make clean`` first.
-```sh
-$ make check
-Recompile: src/rebar_core
-==> rebar (compile)
-Command 'debug' not understood or not applicable
-Congratulations! You now have a self-contained script called "rebar" in
-your current working directory. Place this script anywhere in your path
-and you can use rebar to build OTP-compliant apps.
-==> rebar (xref)
-make: [dialyzer_warnings] Error 2 (ignored)
-```
-
-[xref](http://www.erlang.org/doc/man/xref.html) and
[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) warnings are compared
-against a set of safe-to-ignore warnings
-found in
-[dialyzer_reference](https://raw.github.com/tuncer/rebar/maint/dialyzer_reference)
-and
-[xref_reference](https://raw.github.com/tuncer/rebar/maint/xref_reference).
+against a set of safe-to-ignore warnings found in
+[dialyzer_reference](https://raw.github.com/basho/rebar/master/dialyzer_reference).
+[xref](http://www.erlang.org/doc/man/xref.html) is run with
+[custom queries](https://raw.github.com/basho/rebar/master/rebar.config)
+to suppress safe-to-ignore warnings.
It is **strongly recommended** to check the code with
-[Tidier](http://tidier.softlab.ntua.gr:20000/tidier/getstarted).
-Select all transformation options and enable **automatic**
-transformation.
-If Tidier suggests a transformation apply the changes **manually**
-to the source code.
-Do not use the code from the tarball (*out.tgz*) as it will have
-white-space changes
-applied by Erlang's pretty-printer.
+[Tidier](http://tidier.softlab.ntua.gr:20000/tidier/getstarted).
+Select all transformation
+options and enable **automatic** transformation. If Tidier suggests a transformation,
+apply the changes **manually** to the source code. Do not use the code from
+the
+tarball (*out.tgz*) as it will have white-space changes applied by Erlang's pretty-printer.
+
+Community and Resources
+-----------------------
+
+In case of problems that cannot be solved through documentation or examples, you may
+want to try to contact members of the community for help. The community is also where
+you want to go for questions about how to extend rebar, fill in bug reports, and so on.
+
+The main place to go for questions is the [rebar mailing list](http://lists.basho.com/pipermail/rebar_lists.basho.com/). If you need quick feedback,
+you can try the #rebar channel on [irc.freenode.net](http://freenode.net). Be sure to check the [wiki](https://github.com/basho/rebar/wiki) first,
+just to be sure you're not asking about things with well known answers.
+
+For bug reports, roadmaps, and issues, visit the [github issues page](https://github.com/basho/rebar/issues).
+
+General rebar community resources and links:
+
+- [Rebar Mailing List](http://lists.basho.com/pipermail/rebar_lists.basho.com/)
+- #rebar on [irc.freenode.net](http://freenode.net/)
+- [wiki](https://github.com/basho/rebar/wiki)
+- [issues](https://github.com/basho/rebar/issues)
diff --git a/THANKS b/THANKS
index f38b7e6..16b1f24 100644
--- a/THANKS
+++ b/THANKS
@@ -82,7 +82,7 @@ Ali Sabil
Tomas Abrahamsson
Francis Joanis
fisher@yun.io
-Yurin Slava
+Slava Yurin
Phillip Toland
Mike Lazar
Loic Hoguin
@@ -91,3 +91,21 @@ Adam Schepis
Amit Kapoor
Ulf Wiger
Nick Vatamaniuc
+Daniel Luna
+Motiejus Jakstys
+Eric B Merritt
+Fred Hebert
+Kresten Krab Thorup
+David Aaberg
+DeadZen
+Edwin Fine
+Lev Walkin
+Roberto Ostinelli
+Joe DeVivo
+Markus Nasman
+Dmitriy Kargapolov
+Ryan Zezeski
+Daniel White
+Martin Schut
+Serge Aleynikov
+Magnus Henoch
diff --git a/bootstrap b/bootstrap
index fc6d1a8..70d8da1 100755
--- a/bootstrap
+++ b/bootstrap
@@ -7,15 +7,16 @@ main(Args) ->
Built = build_time(),
%% Get a string repr of first matching VCS changeset
- VcsInfo = vcs_info([{hg, ".hg", "hg identify -i"},
- {git, ".git", "git describe --always --tags"}]),
+ VcsInfo = vcs_info([{hg, ".hg", "hg identify -i", "hg status"},
+ {git, ".git", "git describe --always --tags",
+ "git status -s"}]),
%% Check for force=1 flag to force a rebuild
case lists:member("force=1", Args) of
true ->
rm("ebin/*.beam");
false ->
- rm("ebin/rebar_core.beam")
+ rm("ebin/rebar.beam")
end,
%% Add check for debug flag
@@ -93,10 +94,17 @@ build_time() ->
vcs_info([]) ->
"No VCS info available.";
-vcs_info([{Id, Dir, Cmd} | Rest]) ->
+vcs_info([{Id, Dir, VsnCmd, StatusCmd} | Rest]) ->
case filelib:is_dir(Dir) of
true ->
- lists:concat([Id, " ", string:strip(os:cmd(Cmd), both, $\n)]);
+ Vsn = string:strip(os:cmd(VsnCmd), both, $\n),
+ Status = case string:strip(os:cmd(StatusCmd), both, $\n) of
+ [] ->
+ "";
+ _ ->
+ "-dirty"
+ end,
+ lists:concat([Id, " ", Vsn, Status]);
false ->
vcs_info(Rest)
end.
diff --git a/dialyzer_reference b/dialyzer_reference
index fd49b6a..ff3dc50 100644
--- a/dialyzer_reference
+++ b/dialyzer_reference
@@ -1,2 +1,3 @@
-rebar_utils.erl:158: Call to missing or unexported function escript:foldl/3
+rebar_eunit.erl:351: Call to missing or unexported function eunit_test:function_wrapper/2
+rebar_utils.erl:163: Call to missing or unexported function escript:foldl/3
diff --git a/ebin/rebar.app b/ebin/rebar.app
index b1a51c7..f4444a3 100644
--- a/ebin/rebar.app
+++ b/ebin/rebar.app
@@ -27,9 +27,11 @@
rebar_otp_app,
rebar_port_compiler,
rebar_protobuffs_compiler,
+ rebar_qc,
rebar_rel_utils,
rebar_reltool,
rebar_require_vsn,
+ rebar_shell,
rebar_subdirs,
rebar_templater,
rebar_upgrade,
@@ -49,9 +51,6 @@
%% Default log level
{log_level, error},
- %% Default parallel jobs
- {jobs, 3},
-
%% any_dir processing modules
{any_dir_modules, [
rebar_require_vsn,
@@ -75,8 +74,10 @@
rebar_otp_app,
rebar_ct,
rebar_eunit,
+ rebar_qc,
rebar_escripter,
rebar_edoc,
+ rebar_shell,
rebar_xref
]},
diff --git a/include/rebar.hrl b/include/rebar.hrl
index 7568898..58debfc 100644
--- a/include/rebar.hrl
+++ b/include/rebar.hrl
@@ -1,5 +1,7 @@
-
--define(ABORT, rebar_utils:abort()).
+%% TODO: rename FAIL to ABORT once we require at least R13B04 for
+%% building rebar. Macros with different arity were not supported by the
+%% compiler before 13B04.
+-define(FAIL, rebar_utils:abort()).
-define(ABORT(Str, Args), rebar_utils:abort(Str, Args)).
-define(CONSOLE(Str, Args), io:format(Str, Args)).
diff --git a/inttest/ct1/rebar.config b/inttest/ct1/rebar.config
index 839fe4b..a4b5284 100644
--- a/inttest/ct1/rebar.config
+++ b/inttest/ct1/rebar.config
@@ -1,2 +1 @@
-
{ct_dir, "itest"}.
diff --git a/inttest/ct2/ct2_rt.erl b/inttest/ct2/ct2_rt.erl
index 2b14ff9..ecab0e4 100644
--- a/inttest/ct2/ct2_rt.erl
+++ b/inttest/ct2/ct2_rt.erl
@@ -6,11 +6,11 @@
files() ->
[{create, "ebin/foo.app", app(foo)},
{copy, "../../rebar", "rebar"},
- {copy, "foo.test.spec", "test/foo.test.spec"},
+ {copy, "foo.test.spec", "foo.test.spec"},
{copy, "foo_SUITE.erl", "test/foo_SUITE.erl"}].
run(_Dir) ->
- {ok, _} = retest:sh("./rebar compile ct -v"),
+ {ok, _} = retest:sh("./rebar compile ct -vvv"),
ok.
%%
diff --git a/inttest/t_custom_config/custom.config b/inttest/t_custom_config/custom.config
index 8c60b5e..711c27f 100644
--- a/inttest/t_custom_config/custom.config
+++ b/inttest/t_custom_config/custom.config
@@ -1,4 +1,3 @@
-
{deps, [
{boo, "."}
]}.
diff --git a/inttest/t_custom_config/t_custom_config_rt.erl b/inttest/t_custom_config/t_custom_config_rt.erl
index d333b11..864ce5e 100644
--- a/inttest/t_custom_config/t_custom_config_rt.erl
+++ b/inttest/t_custom_config/t_custom_config_rt.erl
@@ -11,16 +11,17 @@ files() ->
run(Dir) ->
retest_log:log(debug, "Running in Dir: ~s~n", [Dir]),
- Ref = retest:sh("./rebar -C custom.config check-deps -v", [{async, true}]),
+ Ref = retest:sh("./rebar -C custom.config check-deps -vvv",
+ [{async, true}]),
{ok, Captured} =
retest:sh_expect(Ref,
"DEBUG: Consult config file .*/custom.config.*",
[{capture, all, list}]),
{ok, Missing} =
retest:sh_expect(Ref,
- "DEBUG: Missing deps : \\[\\{dep,bad_name,"
- "boo,\"\\.\",undefined\\}\\]",
- [{capture, all, list}]),
+ "DEBUG: Missing deps : \\[\\{dep,bad_name,"
+ "boo,\"\\.\",undefined,false\\}\\]",
+ [{capture, all, list}]),
retest_log:log(debug, "[CAPTURED]: ~s~n", [Captured]),
retest_log:log(debug, "[Missing]: ~s~n", [Missing]),
ok.
diff --git a/inttest/tdeps1/a.erl b/inttest/tdeps1/a.erl
index e64b16d..835522a 100644
--- a/inttest/tdeps1/a.erl
+++ b/inttest/tdeps1/a.erl
@@ -6,4 +6,3 @@
hello() ->
io:format("~s\n", [?HELLO]).
-
diff --git a/inttest/tdeps1/a.rebar.config b/inttest/tdeps1/a.rebar.config
index afeef7a..01609ee 100644
--- a/inttest/tdeps1/a.rebar.config
+++ b/inttest/tdeps1/a.rebar.config
@@ -1,2 +1 @@
{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
-
diff --git a/inttest/tdeps1/b.hrl b/inttest/tdeps1/b.hrl
index 897f348..efbeab1 100644
--- a/inttest/tdeps1/b.hrl
+++ b/inttest/tdeps1/b.hrl
@@ -1,3 +1 @@
-
-include_lib("c/include/c.hrl").
-
diff --git a/inttest/tdeps1/b.rebar.config b/inttest/tdeps1/b.rebar.config
index ced29cc..59c8987 100644
--- a/inttest/tdeps1/b.rebar.config
+++ b/inttest/tdeps1/b.rebar.config
@@ -1,2 +1 @@
{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
-
diff --git a/inttest/tdeps1/c.hrl b/inttest/tdeps1/c.hrl
index 84cf2d4..9f02fab 100644
--- a/inttest/tdeps1/c.hrl
+++ b/inttest/tdeps1/c.hrl
@@ -1,2 +1 @@
-define(HELLO, hello).
-
diff --git a/inttest/tdeps1/tdeps1_rt.erl b/inttest/tdeps1/tdeps1_rt.erl
index 9072e9c..43184c6 100644
--- a/inttest/tdeps1/tdeps1_rt.erl
+++ b/inttest/tdeps1/tdeps1_rt.erl
@@ -30,14 +30,11 @@ run(_Dir) ->
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]),
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]),
-
{ok, _} = retest_sh:run("./rebar get-deps compile", []),
true = filelib:is_regular("ebin/a.beam"),
ok.
-
-
%%
%% Generate the contents of a simple .app file
%%
diff --git a/inttest/tdeps2/a.erl b/inttest/tdeps2/a.erl
index 29538e0..294ae21 100644
--- a/inttest/tdeps2/a.erl
+++ b/inttest/tdeps2/a.erl
@@ -1,4 +1,3 @@
-module({{module}}).
-include_lib("b/include/b.hrl").
-
diff --git a/inttest/tdeps2/a.rebar.config b/inttest/tdeps2/a.rebar.config
index afeef7a..01609ee 100644
--- a/inttest/tdeps2/a.rebar.config
+++ b/inttest/tdeps2/a.rebar.config
@@ -1,2 +1 @@
{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
-
diff --git a/inttest/tdeps2/b.hrl b/inttest/tdeps2/b.hrl
index 897f348..efbeab1 100644
--- a/inttest/tdeps2/b.hrl
+++ b/inttest/tdeps2/b.hrl
@@ -1,3 +1 @@
-
-include_lib("c/include/c.hrl").
-
diff --git a/inttest/tdeps2/b.rebar.config b/inttest/tdeps2/b.rebar.config
index ced29cc..59c8987 100644
--- a/inttest/tdeps2/b.rebar.config
+++ b/inttest/tdeps2/b.rebar.config
@@ -1,2 +1 @@
{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
-
diff --git a/inttest/tdeps2/c.hrl b/inttest/tdeps2/c.hrl
index 84cf2d4..9f02fab 100644
--- a/inttest/tdeps2/c.hrl
+++ b/inttest/tdeps2/c.hrl
@@ -1,2 +1 @@
-define(HELLO, hello).
-
diff --git a/inttest/tdeps2/tdeps2_rt.erl b/inttest/tdeps2/tdeps2_rt.erl
index cdf10b5..bad546e 100644
--- a/inttest/tdeps2/tdeps2_rt.erl
+++ b/inttest/tdeps2/tdeps2_rt.erl
@@ -38,12 +38,9 @@ run(_Dir) ->
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]),
{ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]),
-
{ok, _} = retest_sh:run("./rebar -v get-deps compile", []),
ok.
-
-
%%
%% Generate the contents of a simple .app file
%%
diff --git a/inttest/tplugins/tplugins_rt.erl b/inttest/tplugins/tplugins_rt.erl
index d2ef382..d6908ad 100644
--- a/inttest/tplugins/tplugins_rt.erl
+++ b/inttest/tplugins/tplugins_rt.erl
@@ -18,7 +18,7 @@ files() ->
{create, "ebin/fish.app", app(fish, [fish])}
].
-run(Dir) ->
+run(_Dir) ->
?assertMatch({ok, _}, retest_sh:run("./rebar fwibble -v", [])),
?assertEqual(false, filelib:is_regular("fwibble.test")),
Ref = retest:sh("./rebar -C bad.config -v clean", [{async, true}]),
diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar
index 005325d..d76e2ba 100644
--- a/priv/shell-completion/bash/rebar
+++ b/priv/shell-completion/bash/rebar
@@ -10,9 +10,10 @@ _rebar()
lopts=" --help --commands --verbose --force --jobs= --version"
cmdsnvars="check-deps clean compile create create-app create-node ct \
doc delete-deps escriptize eunit get-deps generate generate-upgrade \
- help list-deps list-templates update-deps version xref overlay \
- apps= case= force=1 jobs= suites= verbose=1 appid= previous_release= \
- nodeid= root_dir= skip_deps=true skip_apps= template= template_dir="
+ help list-deps list-templates qc update-deps version xref overlay \
+ apps= case= force=1 jobs= suites= verbose=1 appid= \
+ previous_release= nodeid= root_dir= skip_deps=true skip_apps= \
+ template= template_dir="
if [[ ${cur} == --* ]] ; then
COMPREPLY=( $(compgen -W "${lopts}" -- ${cur}) )
diff --git a/priv/templates/simplenode.runner b/priv/templates/simplenode.runner
index 0293340..43d90bc 100755
--- a/priv/templates/simplenode.runner
+++ b/priv/templates/simplenode.runner
@@ -4,9 +4,10 @@
RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd)
+CALLER_DIR=$PWD
+
RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}
RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc
-RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log
# Note the trailing slash on $PIPE_DIR/
PIPE_DIR=/tmp/$RUNNER_BASE_DIR/
RUNNER_USER=
@@ -16,11 +17,6 @@ if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then
exec sudo -u $RUNNER_USER -i $0 $@
fi
-# Make sure CWD is set to runner base dir
-cd $RUNNER_BASE_DIR
-
-# Make sure log directory exists
-mkdir -p $RUNNER_LOG_DIR
# Identify the script name
SCRIPT=`basename $0`
@@ -29,18 +25,32 @@ START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
ERTS_VSN=${START_ERL% *}
APP_VSN=${START_ERL#* }
-# Use releases/VSN/vm.args if it exists otherwise use etc/vm.args
-if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then
- VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args"
+# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or else etc/vm.args
+if [ -e "$CALLER_DIR/vm.args" ]; then
+ VMARGS_PATH=$CALLER_DIR/vm.args
+ USE_DIR=$CALLER_DIR
else
- VMARGS_PATH="$RUNNER_ETC_DIR/vm.args"
+ USE_DIR=$RUNNER_BASE_DIR
+ if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then
+ VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args"
+ else
+ VMARGS_PATH="$RUNNER_ETC_DIR/vm.args"
+ fi
fi
+RUNNER_LOG_DIR=$USE_DIR/log
+# Make sure log directory exists
+mkdir -p $RUNNER_LOG_DIR
+
# Use releases/VSN/sys.config if it exists otherwise use etc/app.config
-if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then
- CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config"
+if [ -e "$USE_DIR/sys.config" ]; then
+ CONFIG_PATH="$USE_DIR/sys.config"
else
- CONFIG_PATH="$RUNNER_ETC_DIR/app.config"
+ if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then
+ CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config"
+ else
+ CONFIG_PATH="$RUNNER_ETC_DIR/app.config"
+ fi
fi
# Extract the target node name from node.args
@@ -65,6 +75,13 @@ if [ -z "$COOKIE_ARG" ]; then
exit 1
fi
+# Make sure CWD is set to the right dir
+cd $USE_DIR
+
+# Make sure log directory exists
+mkdir -p $USE_DIR/log
+
+
# Add ERTS bin dir to our path
ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
@@ -76,19 +93,30 @@ REMSH="$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG"
# Check the first argument for instructions
case "$1" in
- start)
+ start|start_boot)
# Make sure there is not already a node running
RES=`$NODETOOL ping`
if [ "$RES" = "pong" ]; then
echo "Node is already running!"
exit 1
fi
- shift # remove $1
+ case "$1" in
+ start)
+ shift
+ START_OPTION="console"
+ HEART_OPTION="start"
+ ;;
+ start_boot)
+ shift
+ START_OPTION="console_boot"
+ HEART_OPTION="start_boot"
+ ;;
+ esac
RUN_PARAM=$(printf "\'%s\' " "$@")
- HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start $RUN_PARAM"
+ HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT $HEART_OPTION $RUN_PARAM"
export HEART_COMMAND
mkdir -p $PIPE_DIR
- $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console $RUN_PARAM" 2>&1
+ $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT $START_OPTION $RUN_PARAM" 2>&1
;;
stop)
@@ -195,12 +223,18 @@ case "$1" in
$ERTS_PATH/escript $RUNNER_BASE_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2
;;
- console|console_clean)
+ console|console_clean|console_boot)
# .boot file typically just $SCRIPT (ie, the app name)
- # however, for debugging, sometimes start_clean.boot is useful:
+ # however, for debugging, sometimes start_clean.boot is useful.
+ # For e.g. 'setup', one may even want to name another boot script.
case "$1" in
console) BOOTFILE=$SCRIPT ;;
console_clean) BOOTFILE=start_clean ;;
+ console_boot)
+ shift
+ BOOTFILE="$1"
+ shift
+ ;;
esac
# Setup beam-required vars
ROOTDIR=$RUNNER_BASE_DIR
@@ -250,7 +284,7 @@ case "$1" in
exec $CMD -- ${1+"$@"}
;;
*)
- echo "Usage: $SCRIPT {start|foreground|stop|restart|reboot|ping|console|console_clean|attach|remote_console|upgrade}"
+ echo "Usage: $SCRIPT {start|start_boot <file>|foreground|stop|restart|reboot|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade}"
exit 1
;;
esac
diff --git a/priv/templates/simplenode.windows.runner.cmd b/priv/templates/simplenode.windows.runner.cmd
index b4e6b00..16da5b9 100644
--- a/priv/templates/simplenode.windows.runner.cmd
+++ b/priv/templates/simplenode.windows.runner.cmd
@@ -9,19 +9,28 @@
@set releases_dir=%node_root%\releases
@rem Parse ERTS version and release version from start_erl.data
-@for /F "tokens=1,2" %%I in (%releases_dir%\start_erl.data) do @(
+@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
@call :set_trim erts_version %%I
@call :set_trim release_version %%J
)
-@rem extract erlang cookie from vm.args
@set vm_args=%releases_dir%\%release_version%\vm.args
-@for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie %vm_args%`) do @set erlang_cookie=%%J
+@set sys_config=%releases_dir%\%release_version%\sys.config
+@set node_boot_script=%releases_dir%\%release_version%\%node_name%
+@set clean_boot_script=%releases_dir%\%release_version%\start_clean
+
+@rem extract erlang cookie from vm.args
+@for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie "%vm_args%"`) do @set erlang_cookie=%%J
@set erts_bin=%node_root%\erts-%erts_version%\bin
@set service_name=%node_name%_%release_version%
+@set erlsrv="%erts_bin%\erlsrv.exe"
+@set epmd="%erts_bin%\epmd.exe"
+@set escript="%erts_bin%\escript.exe"
+@set werl="%erts_bin%\werl.exe"
+
@if "%1"=="usage" @goto usage
@if "%1"=="install" @goto install
@if "%1"=="uninstall" @goto uninstall
@@ -39,34 +48,37 @@
@goto :EOF
:install
-@%erts_bin%\erlsrv.exe add %service_name% -c "Erlang node %node_name% in %node_root%" -sname %node_name% -w %node_root% -m %node_root%\bin\start_erl.cmd -args " ++ %node_name% ++ %node_root%" -stopaction "init:stop()."
+@set description=Erlang node %node_name% in %node_root%
+@set start_erl=%node_root%\bin\start_erl.cmd
+@set args= ++ %node_name% ++ %node_root%
+@%erlsrv% add %service_name% -c "%description%" -sname %node_name% -w "%node_root%" -m "%start_erl%" -args "%args%" -stopaction "init:stop()."
@goto :EOF
:uninstall
-@%erts_bin%\erlsrv.exe remove %service_name%
-@%erts_bin%\epmd.exe -kill
+@%erlsrv% remove %service_name%
+@%epmd% -kill
@goto :EOF
:start
-@%erts_bin%\erlsrv.exe start %service_name%
+@%erlsrv% start %service_name%
@goto :EOF
:stop
-@%erts_bin%\erlsrv.exe stop %service_name%
+@%erlsrv% stop %service_name%
@goto :EOF
:console
-@start %erts_bin%\werl.exe -boot %releases_dir%\%release_version%\%node_name% -config %releases_dir%\%release_version%\sys.config -args_file %vm_args% -sname %node_name%
+@start "%node_name% console" %werl% -boot "%node_boot_script%" -config "%sys_config%" -args_file "%vm_args%" -sname %node_name%
@goto :EOF
:query
-@%erts_bin%\erlsrv.exe list %service_name%
-@exit /b %ERRORLEVEL%
+@%erlsrv% list %service_name%
+@exit %ERRORLEVEL%
@goto :EOF
:attach
@for /f "usebackq" %%I in (`hostname`) do @set hostname=%%I
-start %erts_bin%\werl.exe -boot %releases_dir%\%release_version%\start_clean -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie%
+start "%node_name% attach" %werl% -boot "%clean_boot_script%" -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie%
@goto :EOF
:upgrade
@@ -76,7 +88,7 @@ start %erts_bin%\werl.exe -boot %releases_dir%\%release_version%\start_clean -re
@echo NOTE {package base name} MUST NOT include the .tar.gz suffix
@goto :EOF
)
-@%erts_bin%\escript.exe %node_root%\bin\install_upgrade.escript %node_name% %erlang_cookie% %2
+@%escript% %node_root%\bin\install_upgrade.escript %node_name% %erlang_cookie% %2
@goto :EOF
:set_trim
diff --git a/priv/templates/simplenode.windows.start_erl.cmd b/priv/templates/simplenode.windows.start_erl.cmd
index ef38d52..c0f2072 100644
--- a/priv/templates/simplenode.windows.start_erl.cmd
+++ b/priv/templates/simplenode.windows.start_erl.cmd
@@ -6,30 +6,31 @@
@for /F "delims=++ tokens=1,2,3" %%I in (%args%) do @(
@set erl_args=%%I
@call :set_trim node_name %%J
- @call :set_trim node_root %%K
+ @rem Trim spaces from the left of %%K (node_root), which may have spaces inside
+ @for /f "tokens=* delims= " %%a in ("%%K") do @set node_root=%%a
)
@set releases_dir=%node_root%\releases
@rem parse ERTS version and release version from start_erl.dat
-@for /F "tokens=1,2" %%I in (%releases_dir%\start_erl.data) do @(
+@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
@call :set_trim erts_version %%I
@call :set_trim release_version %%J
)
-@set erl_exe=%node_root%\erts-%erts_version%\bin\erl.exe
-@set boot_file=%releases_dir%\%release_version%\%node_name%
+@set erl_exe="%node_root%\erts-%erts_version%\bin\erl.exe"
+@set boot_file="%releases_dir%\%release_version%\%node_name%"
-@if exist %releases_dir%\%release_version%\sys.config (
- @set app_config=%releases_dir%\%release_version%\sys.config
+@if exist "%releases_dir%\%release_version%\sys.config" (
+ @set app_config="%releases_dir%\%release_version%\sys.config"
) else (
- @set app_config=%node_root%\etc\app.config
+ @set app_config="%node_root%\etc\app.config"
)
-@if exist %releases_dir%\%release_version%\vm.args (
- @set vm_args=%releases_dir%\%release_version%\vm.args
+@if exist "%releases_dir%\%release_version%\vm.args" (
+ @set vm_args="%releases_dir%\%release_version%\vm.args"
) else (
- @set vm_args=%node_root%\etc\vm.args
+ @set vm_args="%node_root%\etc\vm.args"
)
@%erl_exe% %erl_args% -boot %boot_file% -config %app_config% -args_file %vm_args%
diff --git a/rebar.config b/rebar.config
index 9afa8da..cbdaf52 100644
--- a/rebar.config
+++ b/rebar.config
@@ -2,8 +2,6 @@
%% ex: ts=4 sw=4 ft=erlang et
{app_bin, ["priv/rebar"]}.
-{escript_shebang, "#!/usr/bin/env escript\n"}.
-{escript_emu_args, "%%! -noshell -noinput\n"}.
%% escript_incl_extra is for internal rebar-private use only.
%% Do not use outside rebar. Config interface is not stable.
{escript_incl_extra, [{"priv/templates/*", "."}]}.
@@ -12,9 +10,10 @@
{xref_queries,
[{"(XC - UC) || (XU - X - B
- (\"escript\":\"foldl\"/\"3\")
+ - (\"eunit_test\":\"function_wrapper\"/\"2\")
- (\"abnfc\":\"file\"/\"2\")
- (\"erlydtl\":\"compile\"/\"3\")
- (\"lfe_comp\":\"file\"/\"2\")
- (\"neotoma\":\"file\"/\"2\")
- - (\"protobuffs_compile\":\"scan_file\"/\"1\"))",
+ - (\"protobuffs_compile\":\"scan_file\"/\"2\"))",
[]}]}.
diff --git a/rebar.config.sample b/rebar.config.sample
index 0e846f9..fac55af 100644
--- a/rebar.config.sample
+++ b/rebar.config.sample
@@ -65,29 +65,48 @@
%% Options for eunit:test()
{eunit_opts, []}.
-%% Additional compile options for eunit. erl_opts from above is also used
+%% Additional compile options for eunit. erl_opts is also used
{eunit_compile_opts, []}.
%% Same as erl_first_files, but used only when running 'eunit'
{eunit_first_files, []}.
+%% == Cover ==
+
%% Whether to enable coverage reporting. Default is `false'
{cover_enabled, false}.
%% Whether to print coverage report to console. Default is `false'
{cover_print_enabled, false}.
+%% Whether to export coverage report to file. Default is `false'
+{cover_export_enabled, false}.
+
%% == Common Test ==
%% Override the default "test" directory in which SUITEs are located
{ct_dir, "itest"}.
+%% Override the default "logs" directory in which SUITEs are logged
+{ct_log_dir, "test/logs"}.
+
%% Option to pass extra parameters when launching Common Test
{ct_extra_params, "-boot start_sasl -s myapp"}.
%% Option to use short names (i.e., -sname test) when starting ct
{ct_use_short_names, true}.
+%% == QuickCheck ==
+
+%% If qc_mod is unspecified, rebar tries to detect Triq or EQC
+{qc_opts, [{qc_mod, module()}, Options]}.
+
+%% Additional compile options for qc. erl_opts is also used
+{qc_compile_opts, []}.
+
+%% Same as erl_first_files, but used only when running 'qc'
+{qc_first_files, []}.
+
%% == Cleanup ==
%% Which files to cleanup
@@ -114,11 +133,25 @@
%% What dependencies we have, dependencies can be of 3 forms, an application
%% name as an atom, eg. mochiweb, a name and a version (from the .app file), or
%% an application name, a version and the SCM details on how to fetch it (SCM
-%% type, location and revision). Rebar currently supports git, hg, bzr and svn.
+%% type, location and revision).
+%% Rebar currently supports git, hg, bzr, svn, and rsync.
{deps, [application_name,
{application_name, "1.0.*"},
{application_name, "1.0.*",
- {git, "git://github.com/basho/rebar.git", {branch, "master"}}}]}.
+ {git, "git://github.com/basho/rebar.git", {branch, "master"}}},
+
+%% Dependencies can be marked as 'raw'. Rebar does not require such dependencies
+%% to have a standard Erlang/OTP layout which assumes the presence of either
+%% "src/dependency_name.app.src" or "ebin/dependency_name.app" files.
+%%
+%% 'raw' dependencies can still contain 'rebar.config' and even can have the
+%% proper OTP directory layout, but they won't be compiled.
+%%
+%% Only a subset of rebar commands will be executed on the 'raw' subdirectories:
+%% get-deps, update-deps, check-deps, list-deps and delete-deps.
+ {application_name, "",
+ {git, "git://github.com/basho/rebar.git", {branch, "master"}},
+ [raw]}]}.
%% == Subdirectories ==
diff --git a/rebar.config.script b/rebar.config.script
new file mode 100644
index 0000000..07feb95
--- /dev/null
+++ b/rebar.config.script
@@ -0,0 +1,20 @@
+%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 ft=erlang et
+
+%% TODO: Change temporary retest fork back to dizzyd/retest after merge
+%% ExtraDeps = [{retest, ".*", {git, "git://github.com/dizzyd/retest.git"}}],
+ExtraDeps = [{retest, ".*",
+ {git, "git://github.com/tuncer/retest.git", "next"}}],
+
+case os:getenv("REBAR_EXTRA_DEPS") of
+ false ->
+ CONFIG;
+ _ ->
+ case lists:keysearch(deps, 1, CONFIG) of
+ {value, {deps, Deps}} ->
+ NDeps = Deps ++ ExtraDeps,
+ lists:keyreplace(deps, 1, CONFIG, {deps, NDeps});
+ false ->
+ CONFIG ++ [{deps, ExtraDeps}]
+ end
+end.
diff --git a/src/mustache.erl b/src/mustache.erl
index ac501a0..f6963cd 100644
--- a/src/mustache.erl
+++ b/src/mustache.erl
@@ -31,10 +31,6 @@
section_re = undefined,
tag_re = undefined}).
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
compile(Body) when is_list(Body) ->
State = #mstate{},
CompiledTemplate = pre_compile(Body, State),
@@ -113,9 +109,9 @@ compile_section(Name, Content, State) ->
Result = compiler(Content, State),
"fun() -> " ++
"case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++
- "true -> " ++
+ "\"true\" -> " ++
Result ++ "; " ++
- "false -> " ++
+ "\"false\" -> " ++
"[]; " ++
"List when is_list(List) -> " ++
"[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++
@@ -154,9 +150,21 @@ compile_tag("{", Content, State) ->
compile_tag("!", _Content, _State) ->
"[]".
+template_dir(Mod) ->
+ DefaultDirPath = filename:dirname(code:which(Mod)),
+ case application:get_env(mustache, templates_dir) of
+ {ok, DirPath} when is_list(DirPath) ->
+ case filelib:ensure_dir(DirPath) of
+ ok -> DirPath;
+ _ -> DefaultDirPath
+ end;
+ _ ->
+ DefaultDirPath
+ end.
template_path(Mod) ->
- ModPath = code:which(Mod),
- re:replace(ModPath, "\.beam$", ".mustache", [{return, list}]).
+ DirPath = template_dir(Mod),
+ Basename = atom_to_list(Mod),
+ filename:join(DirPath, Basename ++ ".mustache").
get(Key, Ctx) when is_list(Key) ->
{ok, Mod} = dict:find('__mod__', Ctx),
@@ -218,17 +226,3 @@ escape([X | Rest], Acc) ->
start([T]) ->
Out = render(list_to_atom(T)),
io:format(Out ++ "~n", []).
-
--ifdef(TEST).
-
-simple_test() ->
- Ctx = dict:from_list([{name, "world"}]),
- Result = render("Hello {{name}}!", Ctx),
- ?assertEqual("Hello world!", Result).
-
-integer_values_too_test() ->
- Ctx = dict:from_list([{name, "Chris"}, {value, 10000}]),
- Result = render("Hello {{name}}~nYou have just won ${{value}}!", Ctx),
- ?assertEqual("Hello Chris~nYou have just won $10000!", Result).
-
--endif.
diff --git a/src/rebar.erl b/src/rebar.erl
index 3f93ed9..cd0bed5 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -29,7 +29,8 @@
-export([main/1,
help/0,
parse_args/1,
- version/0]).
+ version/0,
+ get_jobs/1]).
-include("rebar.hrl").
@@ -45,6 +46,8 @@
-define(OTP_INFO, "undefined").
-endif.
+-define(DEFAULT_JOBS, 3).
+
%% ====================================================================
%% Public API
%% ====================================================================
@@ -66,72 +69,90 @@ main(Args) ->
%% Internal functions
%% ====================================================================
+run(["help"]) ->
+ help();
+run(["version"]) ->
+ ok = load_rebar_app(),
+ %% Display vsn and build time info
+ version();
run(RawArgs) ->
- %% Pre-load the rebar app so that we get default configuration
- ok = application:load(rebar),
+ ok = load_rebar_app(),
%% Parse out command line arguments -- what's left is a list of commands to
%% run -- and start running commands
Args = parse_args(RawArgs),
+ BaseConfig = init_config(Args),
+ {BaseConfig1, Cmds} = save_options(BaseConfig, Args),
- case rebar_config:get_global(enable_profiling, false) of
+ case rebar_config:get_xconf(BaseConfig1, enable_profiling, false) of
true ->
io:format("Profiling!\n"),
try
- fprof:apply(fun(A) -> run_aux(A) end, [Args])
+ fprof:apply(fun run_aux/2, [BaseConfig1, Cmds])
after
- fprof:profile(),
- fprof:analyse([{dest, "fprof.analysis"}])
+ ok = fprof:profile(),
+ ok = fprof:analyse([{dest, "fprof.analysis"}])
end;
- _ ->
- run_aux(Args)
+ false ->
+ run_aux(BaseConfig1, Cmds)
end.
-run_aux(["help"]) ->
- help(),
- ok;
-run_aux(["version"]) ->
- %% Display vsn and build time info
- version(),
- ok;
-run_aux(Commands) ->
- %% Make sure crypto is running
- ok = crypto:start(),
+load_rebar_app() ->
+ %% Pre-load the rebar app so that we get default configuration
+ ok = application:load(rebar).
+init_config({Options, _NonOptArgs}) ->
+ %% If $HOME/.rebar/config exists load and use as global config
+ GlobalConfigFile = filename:join([os:getenv("HOME"), ".rebar", "config"]),
+ GlobalConfig = case filelib:is_regular(GlobalConfigFile) of
+ true ->
+ ?DEBUG("Load global config file ~p~n",
+ [GlobalConfigFile]),
+ rebar_config:new(GlobalConfigFile);
+ false ->
+ rebar_config:new()
+ end,
+
+ %% Set the rebar config to use
+ GlobalConfig1 = case proplists:get_value(config, Options) of
+ undefined ->
+ GlobalConfig;
+ Conf ->
+ rebar_config:set_global(GlobalConfig, config, Conf)
+ end,
+
+ GlobalConfig2 = set_log_level(GlobalConfig1, Options),
%% Initialize logging system
- rebar_log:init(),
+ ok = rebar_log:init(GlobalConfig2),
+ BaseConfig = rebar_config:base_config(GlobalConfig2),
+
+ %% Keep track of how many operations we do, so we can detect bad commands
+ BaseConfig1 = rebar_config:set_xconf(BaseConfig, operations, 0),
%% Initialize vsn cache
- _VsnCacheTab = ets:new(rebar_vsn_cache,[named_table, public]),
+ rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()).
+
+run_aux(BaseConfig, Commands) ->
+ %% Make sure crypto is running
+ case crypto:start() of
+ ok -> ok;
+ {error,{already_started,crypto}} -> ok
+ end,
%% Convert command strings to atoms
CommandAtoms = [list_to_atom(C) || C <- Commands],
%% Determine the location of the rebar executable; important for pulling
%% resources out of the escript
- rebar_config:set_global(escript, filename:absname(escript:script_name())),
- ?DEBUG("Rebar location: ~p\n",
- [rebar_config:get_global(escript, undefined)]),
+ ScriptName = filename:absname(escript:script_name()),
+ BaseConfig1 = rebar_config:set_xconf(BaseConfig, escript, ScriptName),
+ ?DEBUG("Rebar location: ~p\n", [ScriptName]),
%% Note the top-level directory for reference
- rebar_config:set_global(base_dir, filename:absname(rebar_utils:get_cwd())),
-
- %% Keep track of how many operations we do, so we can detect bad commands
- erlang:put(operations, 0),
-
- %% If $HOME/.rebar/config exists load and use as global config
- GlobalConfigFile = filename:join([os:getenv("HOME"), ".rebar", "config"]),
- GlobalConfig = case filelib:is_regular(GlobalConfigFile) of
- true ->
- ?DEBUG("Load global config file ~p~n",
- [GlobalConfigFile]),
- rebar_config:new(GlobalConfigFile);
- false ->
- rebar_config:new()
- end,
- BaseConfig = rebar_config:base_config(GlobalConfig),
+ AbsCwd = filename:absname(rebar_utils:get_cwd()),
+ BaseConfig2 = rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd),
%% Process each command, resetting any state between each one
- rebar_core:process_commands(CommandAtoms, BaseConfig).
+ rebar_core:process_commands(CommandAtoms, BaseConfig2).
%%
%% print help/usage string
@@ -147,63 +168,59 @@ help() ->
%% Parse command line arguments using getopt and also filtering out any
%% key=value pairs. What's left is the list of commands to run
%%
-parse_args(Args) ->
+parse_args(RawArgs) ->
%% Parse getopt options
OptSpecList = option_spec_list(),
- case getopt:parse(OptSpecList, Args) of
- {ok, {Options, NonOptArgs}} ->
- %% Check options and maybe halt execution
- ok = show_info_maybe_halt(Options, NonOptArgs),
-
- GlobalDefines = proplists:get_all_values(defines, Options),
- rebar_config:set_global(defines, GlobalDefines),
-
- %% Setup profiling flag
- rebar_config:set_global(enable_profiling,
- proplists:get_bool(profile, Options)),
-
- %% Setup flag to keep running after a single command fails
- rebar_config:set_global(keep_going,
- proplists:get_bool(keep_going, Options)),
-
- %% Set global variables based on getopt options
- set_log_level(Options),
- set_global_flag(Options, force),
- DefJobs = rebar_config:get_jobs(),
- case proplists:get_value(jobs, Options, DefJobs) of
- DefJobs ->
- ok;
- Jobs ->
- rebar_config:set_global(jobs, Jobs)
- end,
-
- %% Set the rebar config to use
- case proplists:get_value(config, Options) of
- undefined -> ok;
- Conf -> rebar_config:set_global(config, Conf)
- end,
-
- %% Filter all the flags (i.e. strings of form key=value) from the
- %% command line arguments. What's left will be the commands to run.
- unabbreviate_command_names(filter_flags(NonOptArgs, []));
-
+ case getopt:parse(OptSpecList, RawArgs) of
+ {ok, Args} ->
+ Args;
{error, {Reason, Data}} ->
?ERROR("~s ~p~n~n", [Reason, Data]),
help(),
rebar_utils:delayed_halt(1)
end.
+save_options(Config, {Options, NonOptArgs}) ->
+ %% Check options and maybe halt execution
+ ok = show_info_maybe_halt(Options, NonOptArgs),
+
+ GlobalDefines = proplists:get_all_values(defines, Options),
+
+ Config1 = rebar_config:set_xconf(Config, defines, GlobalDefines),
+
+ %% Setup profiling flag
+ Config2 = rebar_config:set_xconf(Config1, enable_profiling,
+ proplists:get_bool(profile, Options)),
+
+ %% Setup flag to keep running after a single command fails
+ Config3 = rebar_config:set_xconf(Config2, keep_going,
+ proplists:get_bool(keep_going, Options)),
+
+ %% Set global variables based on getopt options
+ Config4 = set_global_flag(Config3, Options, force),
+ Config5 = case proplists:get_value(jobs, Options, ?DEFAULT_JOBS) of
+ ?DEFAULT_JOBS ->
+ Config4;
+ Jobs ->
+ rebar_config:set_global(Config4, jobs, Jobs)
+ end,
+
+ %% Filter all the flags (i.e. strings of form key=value) from the
+ %% command line arguments. What's left will be the commands to run.
+ {Config6, RawCmds} = filter_flags(Config5, NonOptArgs, []),
+ {Config6, unabbreviate_command_names(RawCmds)}.
+
%%
%% set log level based on getopt option
%%
-set_log_level(Options) ->
+set_log_level(Config, Options) ->
LogLevel = case proplists:get_all_values(verbose, Options) of
[] ->
rebar_log:default_level();
Verbosities ->
lists:last(Verbosities)
end,
- rebar_config:set_global(verbose, LogLevel).
+ rebar_config:set_global(Config, verbose, LogLevel).
%%
%% show version information and halt
@@ -217,14 +234,14 @@ version() ->
%%
%% set global flag based on getopt option boolean value
%%
-set_global_flag(Options, Flag) ->
+set_global_flag(Config, Options, Flag) ->
Value = case proplists:get_bool(Flag, Options) of
true ->
"1";
false ->
"0"
end,
- rebar_config:set_global(Flag, Value).
+ rebar_config:set_global(Config, Flag, Value).
%%
%% show info and maybe halt execution
@@ -279,9 +296,21 @@ generate-upgrade previous_release=path Build an upgrade package
generate-appups previous_release=path Generate appup files
-eunit [suites=foo] Run eunit [test/foo_tests.erl] tests
+eunit [suites=foo] Run eunit tests in foo.erl and
+ test/foo_tests.erl
+ [suites=foo] [tests=bar] Run specific eunit tests [first test name
+ starting with 'bar' in foo.erl and
+ test/foo_tests.erl]
+ [tests=bar] For every existing suite, run the first
+ test whose name starts with bar and, if
+ no such test exists, run the test whose
+ name starts with bar in the suite's
+ _tests module
+
ct [suites=] [case=] Run common_test suites
+qc Test QuickCheck properties
+
xref Run cross reference analysis
help Show the program options
@@ -289,11 +318,14 @@ version Show version information
">>,
io:put_chars(S).
+get_jobs(Config) ->
+ rebar_config:get_global(Config, jobs, ?DEFAULT_JOBS).
+
%%
%% options accepted via getopt
%%
option_spec_list() ->
- Jobs = rebar_config:get_jobs(),
+ Jobs = ?DEFAULT_JOBS,
JobsHelp = io_lib:format(
"Number of concurrent workers a command may use. Default: ~B",
[Jobs]),
@@ -317,12 +349,12 @@ option_spec_list() ->
%% Seperate all commands (single-words) from flags (key=value) and store
%% values into the rebar_config global storage.
%%
-filter_flags([], Commands) ->
- lists:reverse(Commands);
-filter_flags([Item | Rest], Commands) ->
+filter_flags(Config, [], Commands) ->
+ {Config, lists:reverse(Commands)};
+filter_flags(Config, [Item | Rest], Commands) ->
case string:tokens(Item, "=") of
[Command] ->
- filter_flags(Rest, [Command | Commands]);
+ filter_flags(Config, Rest, [Command | Commands]);
[KeyStr, RawValue] ->
Key = list_to_atom(KeyStr),
Value = case Key of
@@ -331,18 +363,18 @@ filter_flags([Item | Rest], Commands) ->
_ ->
RawValue
end,
- rebar_config:set_global(Key, Value),
- filter_flags(Rest, Commands);
+ Config1 = rebar_config:set_global(Config, Key, Value),
+ filter_flags(Config1, Rest, Commands);
Other ->
?CONSOLE("Ignoring command line argument: ~p\n", [Other]),
- filter_flags(Rest, Commands)
+ filter_flags(Config, Rest, Commands)
end.
command_names() ->
["check-deps", "clean", "compile", "create", "create-app", "create-node",
"ct", "delete-deps", "doc", "eunit", "generate", "generate-appups",
"generate-upgrade", "get-deps", "help", "list-deps", "list-templates",
- "update-deps", "overlay", "version", "xref"].
+ "qc", "update-deps", "overlay", "shell", "version", "xref"].
unabbreviate_command_names([]) ->
[];
diff --git a/src/rebar_abnfc_compiler.erl b/src/rebar_abnfc_compiler.erl
index cb56854..0e6749a 100644
--- a/src/rebar_abnfc_compiler.erl
+++ b/src/rebar_abnfc_compiler.erl
@@ -90,7 +90,7 @@ compile_abnfc(Source, _Target, Config) ->
" https://github.com/nygge/abnfc~n"
" and install it into your erlang library dir~n"
"===============================================~n~n", []),
- ?ABORT;
+ ?FAIL;
true ->
AbnfcOpts = abnfc_opts(Config),
SourceExt = option(source_ext, AbnfcOpts),
@@ -103,6 +103,6 @@ compile_abnfc(Source, _Target, Config) ->
Error ->
?ERROR("Compiling grammar ~s failed:~n ~p~n",
[Source, Error]),
- ?ABORT
+ ?FAIL
end
end.
diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl
index dbc2c44..8158eb6 100644
--- a/src/rebar_app_utils.erl
+++ b/src/rebar_app_utils.erl
@@ -29,12 +29,12 @@
-export([is_app_dir/0, is_app_dir/1,
is_app_src/1,
app_src_to_app/1,
- app_name/1,
- app_applications/1,
- app_vsn/1,
- is_skipped_app/1]).
+ app_name/2,
+ app_applications/2,
+ app_vsn/2,
+ is_skipped_app/2]).
--export([load_app_file/1]). % TEMPORARY
+-export([load_app_file/2]). % TEMPORARY
-include("rebar.hrl").
@@ -77,75 +77,102 @@ is_app_src(Filename) ->
app_src_to_app(Filename) ->
filename:join("ebin", filename:basename(Filename, ".app.src") ++ ".app").
-app_name(AppFile) ->
- case load_app_file(AppFile) of
- {ok, AppName, _} ->
- AppName;
+app_name(Config, AppFile) ->
+ case load_app_file(Config, AppFile) of
+ {ok, NewConfig, AppName, _} ->
+ {NewConfig, AppName};
{error, Reason} ->
?ABORT("Failed to extract name from ~s: ~p\n",
[AppFile, Reason])
end.
-app_applications(AppFile) ->
- case load_app_file(AppFile) of
- {ok, _, AppInfo} ->
- get_value(applications, AppInfo, AppFile);
+app_applications(Config, AppFile) ->
+ case load_app_file(Config, AppFile) of
+ {ok, NewConfig, _, AppInfo} ->
+ {NewConfig, get_value(applications, AppInfo, AppFile)};
{error, Reason} ->
?ABORT("Failed to extract applications from ~s: ~p\n",
[AppFile, Reason])
end.
-app_vsn(AppFile) ->
- case load_app_file(AppFile) of
- {ok, _, AppInfo} ->
+app_vsn(Config, AppFile) ->
+ case load_app_file(Config, AppFile) of
+ {ok, Config1, _, AppInfo} ->
AppDir = filename:dirname(filename:dirname(AppFile)),
- rebar_utils:vcs_vsn(get_value(vsn, AppInfo, AppFile), AppDir);
+ rebar_utils:vcs_vsn(Config1, get_value(vsn, AppInfo, AppFile),
+ AppDir);
{error, Reason} ->
?ABORT("Failed to extract vsn from ~s: ~p\n",
[AppFile, Reason])
end.
-is_skipped_app(AppFile) ->
- ThisApp = app_name(AppFile),
+is_skipped_app(Config, AppFile) ->
+ {Config1, ThisApp} = app_name(Config, AppFile),
%% Check for apps global parameter; this is a comma-delimited list
%% of apps on which we want to run commands
- case get_apps() of
- undefined ->
- %% No apps parameter specified, check the skip_apps list..
- case get_skip_apps() of
- undefined ->
- %% No skip_apps list, run everything..
- false;
- SkipApps ->
- TargetApps = [list_to_atom(A) ||
- A <- string:tokens(SkipApps, ",")],
- is_skipped_app(ThisApp, TargetApps)
- end;
- Apps ->
- %% run only selected apps
- TargetApps = [list_to_atom(A) || A <- string:tokens(Apps, ",")],
- is_selected_app(ThisApp, TargetApps)
- end.
+ Skipped =
+ case get_apps(Config) of
+ undefined ->
+ %% No apps parameter specified, check the skip_apps list..
+ case get_skip_apps(Config) of
+ undefined ->
+ %% No skip_apps list, run everything..
+ false;
+ SkipApps ->
+ TargetApps = [list_to_atom(A) ||
+ A <- string:tokens(SkipApps, ",")],
+ is_skipped(ThisApp, TargetApps)
+ end;
+ Apps ->
+ %% run only selected apps
+ TargetApps = [list_to_atom(A) || A <- string:tokens(Apps, ",")],
+ is_selected(ThisApp, TargetApps)
+ end,
+ {Config1, Skipped}.
%% ===================================================================
%% Internal functions
%% ===================================================================
-load_app_file(Filename) ->
+load_app_file(Config, Filename) ->
AppFile = {app_file, Filename},
- case erlang:get(AppFile) of
+ case rebar_config:get_xconf(Config, {appfile, AppFile}, undefined) of
undefined ->
- case file:consult(Filename) of
+ case consult_app_file(Filename) of
{ok, [{application, AppName, AppData}]} ->
- erlang:put(AppFile, {AppName, AppData}),
- {ok, AppName, AppData};
+ Config1 = rebar_config:set_xconf(Config,
+ {appfile, AppFile},
+ {AppName, AppData}),
+ {ok, Config1, AppName, AppData};
{error, _} = Error ->
Error;
Other ->
{error, {unexpected_terms, Other}}
end;
{AppName, AppData} ->
- {ok, AppName, AppData}
+ {ok, Config, AppName, AppData}
+ end.
+
+%% In the case of *.app.src we want to give the user the ability to
+%% dynamically script the application resource file (think dynamic version
+%% string, etc.), in a way similar to what can be done with the rebar
+%% config. However, in the case of *.app, rebar should not manipulate
+%% that file. This enforces that dichotomy between app and app.src.
+consult_app_file(Filename) ->
+ case lists:suffix(".app.src", Filename) of
+ false ->
+ file:consult(Filename);
+ true ->
+ %% TODO: EXPERIMENTAL For now let's warn the user if a
+ %% script is going to be run.
+ case filelib:is_regular([Filename, ".script"]) of
+ true ->
+ ?CONSOLE("NOTICE: Using experimental *.app.src.script "
+ "functionality on ~s ~n", [Filename]);
+ _ ->
+ ok
+ end,
+ rebar_config:consult_file(Filename)
end.
get_value(Key, AppInfo, AppFile) ->
@@ -157,7 +184,7 @@ get_value(Key, AppInfo, AppFile) ->
end.
%% apps= for selecting apps
-is_selected_app(ThisApp, TargetApps) ->
+is_selected(ThisApp, TargetApps) ->
case lists:member(ThisApp, TargetApps) of
false ->
{true, ThisApp};
@@ -166,7 +193,7 @@ is_selected_app(ThisApp, TargetApps) ->
end.
%% skip_apps= for filtering apps
-is_skipped_app(ThisApp, TargetApps) ->
+is_skipped(ThisApp, TargetApps) ->
case lists:member(ThisApp, TargetApps) of
false ->
false;
@@ -174,8 +201,8 @@ is_skipped_app(ThisApp, TargetApps) ->
{true, ThisApp}
end.
-get_apps() ->
- rebar_utils:get_deprecated_global(app, apps, "soon").
+get_apps(Config) ->
+ rebar_config:get_global(Config, apps, undefined).
-get_skip_apps() ->
- rebar_utils:get_deprecated_global(skip_app, skip_apps, "soon").
+get_skip_apps(Config) ->
+ rebar_config:get_global(Config, skip_apps, undefined).
diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl
index 6271e77..0aeccb6 100644
--- a/src/rebar_appups.erl
+++ b/src/rebar_appups.erl
@@ -38,13 +38,14 @@
%% Public API
%% ====================================================================
-'generate-appups'(_Config, ReltoolFile) ->
+'generate-appups'(Config, ReltoolFile) ->
%% Get the old release path
- ReltoolConfig = rebar_rel_utils:load_config(ReltoolFile),
- TargetParentDir = rebar_rel_utils:get_target_parent_dir(ReltoolConfig),
+ {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
+ TargetParentDir = rebar_rel_utils:get_target_parent_dir(Config,
+ ReltoolConfig),
- OldVerPath = filename:join([TargetParentDir,
- rebar_rel_utils:get_previous_release_path()]),
+ PrevRelPath = rebar_rel_utils:get_previous_release_path(Config),
+ OldVerPath = filename:join([TargetParentDir, PrevRelPath]),
%% Get the new and old release name and versions
{Name, _Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
@@ -75,7 +76,7 @@
%% Generate appup files for upgraded apps
generate_appup_files(NewVerPath, OldVerPath, UpgradeApps),
- ok.
+ {ok, Config1}.
%% ===================================================================
%% Internal functions
diff --git a/src/rebar_asn1_compiler.erl b/src/rebar_asn1_compiler.erl
index c9dca1f..d93a2e8 100644
--- a/src/rebar_asn1_compiler.erl
+++ b/src/rebar_asn1_compiler.erl
@@ -36,13 +36,13 @@
%% Public API
%% ===================================================================
--spec compile(Config::rebar_config:config(), AppFile::file:filename()) -> 'ok'.
+-spec compile(rebar_config:config(), file:filename()) -> 'ok'.
compile(Config, _AppFile) ->
rebar_base_compiler:run(Config, filelib:wildcard("asn1/*.asn1"),
"asn1", ".asn1", "src", ".erl",
fun compile_asn1/3).
--spec clean(Config::rebar_config:config(), AppFile::file:filename()) -> 'ok'.
+-spec clean(rebar_config:config(), file:filename()) -> 'ok'.
clean(_Config, _AppFile) ->
GeneratedFiles = asn_generated_files("asn1", "src", "include"),
ok = rebar_file_utils:delete_each(GeneratedFiles),
@@ -65,7 +65,7 @@ compile_asn1(Source, Target, Config) ->
ok
end;
{error, _Reason} ->
- ?ABORT
+ ?FAIL
end.
asn_generated_files(AsnDir, SrcDir, IncDir) ->
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index 7d1fb22..63e408b 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -29,8 +29,7 @@
-include("rebar.hrl").
-export([run/4, run/7, run/8,
- ok_tuple/2, error_tuple/4]).
-
+ ok_tuple/3, error_tuple/5]).
%% ===================================================================
%% Public API
@@ -47,7 +46,7 @@ run(Config, FirstFiles, RestFiles, CompileFn) ->
_ ->
Self = self(),
F = fun() -> compile_worker(Self, Config, CompileFn) end,
- Jobs = rebar_config:get_jobs(),
+ Jobs = rebar:get_jobs(Config),
?DEBUG("Starting ~B compile worker(s)~n", [Jobs]),
Pids = [spawn_monitor(F) || _I <- lists:seq(1,Jobs)],
compile_queue(Pids, RestFiles)
@@ -80,11 +79,12 @@ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt,
simple_compile_wrapper(S, Target, Compile3Fn, C, CheckLastMod)
end).
-ok_tuple(Source, Ws) ->
- {ok, format_warnings(Source, Ws)}.
+ok_tuple(Config, Source, Ws) ->
+ {ok, format_warnings(Config, Source, Ws)}.
-error_tuple(Source, Es, Ws, Opts) ->
- {error, format_errors(Source, Es), format_warnings(Source, Ws, Opts)}.
+error_tuple(Config, Source, Es, Ws, Opts) ->
+ {error, format_errors(Config, Source, Es),
+ format_warnings(Config, Source, Ws, Opts)}.
%% ===================================================================
%% Internal functions
@@ -141,7 +141,7 @@ compile_each([Source | Rest], Config, CompileFn) ->
Error ->
maybe_report(Error),
?DEBUG("Compilation failed: ~p\n", [Error]),
- ?ABORT
+ ?FAIL
end,
compile_each(Rest, Config, CompileFn).
@@ -162,7 +162,7 @@ compile_queue(Pids, Targets) ->
{fail, Error} ->
maybe_report(Error),
?DEBUG("Worker compilation failed: ~p\n", [Error]),
- ?ABORT;
+ ?FAIL;
{compiled, Source, Warnings} ->
report(Warnings),
@@ -184,7 +184,7 @@ compile_queue(Pids, Targets) ->
{'DOWN', _Mref, _, _Pid, Info} ->
?DEBUG("Worker failed: ~p\n", [Info]),
- ?ABORT
+ ?FAIL
end.
compile_worker(QueuePid, Config, CompileFn) ->
@@ -211,18 +211,18 @@ compile_worker(QueuePid, Config, CompileFn) ->
ok
end.
-format_errors(Source, Errors) ->
- format_errors(Source, "", Errors).
+format_errors(Config, Source, Errors) ->
+ format_errors(Config, Source, "", Errors).
-format_warnings(Source, Warnings) ->
- format_warnings(Source, Warnings, []).
+format_warnings(Config, Source, Warnings) ->
+ format_warnings(Config, Source, Warnings, []).
-format_warnings(Source, Warnings, Opts) ->
+format_warnings(Config, Source, Warnings, Opts) ->
Prefix = case lists:member(warnings_as_errors, Opts) of
true -> "";
false -> "Warning: "
end,
- format_errors(Source, Prefix, Warnings).
+ format_errors(Config, Source, Prefix, Warnings).
maybe_report([{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}]) ->
maybe_report(ErrorsAndWarnings);
@@ -235,10 +235,17 @@ maybe_report(_) ->
report(Messages) ->
lists:foreach(fun(Msg) -> io:format("~s", [Msg]) end, Messages).
-format_errors(Source, Extra, Errors) ->
- AbsSource = filename:absname(Source),
- [[format_error(AbsSource, Extra, Desc) || Desc <- Descs]
- || {_, Descs} <- Errors].
+format_errors(Config, _MainSource, Extra, Errors) ->
+ [begin
+ AbsSource = case rebar_utils:processing_base_dir(Config) of
+ true ->
+ Source;
+ false ->
+ filename:absname(Source)
+ end,
+ [format_error(AbsSource, Extra, Desc) || Desc <- Descs]
+ end
+ || {Source, Descs} <- Errors].
format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) ->
ErrorDesc = Mod:format_error(Desc),
diff --git a/src/rebar_config.erl b/src/rebar_config.erl
index 7f7d03c..461de5d 100644
--- a/src/rebar_config.erl
+++ b/src/rebar_config.erl
@@ -30,29 +30,36 @@
get/3, get_local/3, get_list/3,
get_all/2,
set/3,
- set_global/2, get_global/2,
- is_verbose/0, get_jobs/0,
- set_env/3, get_env/2]).
+ set_global/3, get_global/3,
+ is_verbose/1,
+ save_env/3, get_env/2, reset_envs/1,
+ set_skip_dir/2, is_skip_dir/2, reset_skip_dirs/1,
+ clean_config/2,
+ set_xconf/3, get_xconf/2, get_xconf/3, erase_xconf/2]).
-include("rebar.hrl").
-record(config, { dir :: file:filename(),
opts = [] :: list(),
- envs = new_env() :: dict() }).
+ globals = new_globals() :: dict(),
+ envs = new_env() :: dict(),
+ %% cross-directory/-command config
+ skip_dirs = new_skip_dirs() :: dict(),
+ xconf = new_xconf() :: dict() }).
-%% Types that can be used from other modules -- alphabetically ordered.
-export_type([config/0]).
-%% data types
-opaque config() :: #config{}.
+-define(DEFAULT_NAME, "rebar.config").
+
%% ===================================================================
%% Public API
%% ===================================================================
-base_config(#config{opts=Opts0}) ->
- ConfName = rebar_config:get_global(config, "rebar.config"),
- new(Opts0, ConfName).
+base_config(GlobalConfig) ->
+ ConfName = rebar_config:get_global(GlobalConfig, config, ?DEFAULT_NAME),
+ new(GlobalConfig, ConfName).
new() ->
#config{dir = rebar_utils:get_cwd()}.
@@ -65,31 +72,10 @@ new(ConfigFile) when is_list(ConfigFile) ->
Other ->
?ABORT("Failed to load ~s: ~p~n", [ConfigFile, Other])
end;
-new(_ParentConfig=#config{opts=Opts0})->
- new(Opts0, "rebar.config").
-
-new(Opts0, ConfName) ->
- %% Load terms from rebar.config, if it exists
- Dir = rebar_utils:get_cwd(),
- ConfigFile = filename:join([Dir, ConfName]),
- Opts = case consult_file(ConfigFile) of
- {ok, Terms} ->
- %% Found a config file with some terms. We need to
- %% be able to distinguish between local definitions
- %% (i.e. from the file in the cwd) and inherited
- %% definitions. To accomplish this, we use a marker
- %% in the proplist (since order matters) between
- %% the new and old defs.
- Terms ++ [local] ++
- [Opt || Opt <- Opts0, Opt /= local];
- {error, enoent} ->
- [local] ++
- [Opt || Opt <- Opts0, Opt /= local];
- Other ->
- ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other])
- end,
-
- #config{dir = Dir, opts = Opts}.
+new(_ParentConfig=#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs,
+ xconf=Xconf}) ->
+ new(#config{opts=Opts0, globals=Globals, skip_dirs=SkipDirs, xconf=Xconf},
+ ?DEFAULT_NAME).
get(Config, Key, Default) ->
proplists:get_value(Key, Config#config.opts, Default).
@@ -107,27 +93,26 @@ set(Config, Key, Value) ->
Opts = proplists:delete(Key, Config#config.opts),
Config#config { opts = [{Key, Value} | Opts] }.
-set_global(jobs=Key, Value) when is_list(Value) ->
- set_global(Key, list_to_integer(Value));
-set_global(jobs=Key, Value) when is_integer(Value) ->
- application:set_env(rebar_global, Key, erlang:max(1, Value));
-set_global(Key, Value) ->
- application:set_env(rebar_global, Key, Value).
-
-get_global(Key, Default) ->
- case application:get_env(rebar_global, Key) of
- undefined ->
+set_global(Config, jobs=Key, Value) when is_list(Value) ->
+ set_global(Config, Key, list_to_integer(Value));
+set_global(Config, jobs=Key, Value) when is_integer(Value) ->
+ NewGlobals = dict:store(Key, erlang:max(1, Value), Config#config.globals),
+ Config#config{globals = NewGlobals};
+set_global(Config, Key, Value) ->
+ NewGlobals = dict:store(Key, Value, Config#config.globals),
+ Config#config{globals = NewGlobals}.
+
+get_global(Config, Key, Default) ->
+ case dict:find(Key, Config#config.globals) of
+ error ->
Default;
{ok, Value} ->
Value
end.
-is_verbose() ->
+is_verbose(Config) ->
DefaulLevel = rebar_log:default_level(),
- get_global(verbose, DefaulLevel) > DefaulLevel.
-
-get_jobs() ->
- get_global(jobs, 3).
+ get_global(Config, verbose, DefaulLevel) > DefaulLevel.
consult_file(File) ->
case filename:extension(File) of
@@ -144,24 +129,90 @@ consult_file(File) ->
end
end.
-set_env(Config, Mod, Env) ->
- OldEnvs = Config#config.envs,
- NewEnvs = dict:store(Mod, Env, OldEnvs),
- Config#config{envs=NewEnvs}.
+save_env(Config, Mod, Env) ->
+ NewEnvs = dict:store(Mod, Env, Config#config.envs),
+ Config#config{envs = NewEnvs}.
get_env(Config, Mod) ->
dict:fetch(Mod, Config#config.envs).
+reset_envs(Config) ->
+ Config#config{envs = new_env()}.
+
+set_skip_dir(Config, Dir) ->
+ OldSkipDirs = Config#config.skip_dirs,
+ NewSkipDirs = case is_skip_dir(Config, Dir) of
+ false ->
+ ?DEBUG("Adding skip dir: ~s\n", [Dir]),
+ dict:store(Dir, true, OldSkipDirs);
+ true ->
+ OldSkipDirs
+ end,
+ Config#config{skip_dirs = NewSkipDirs}.
+
+is_skip_dir(Config, Dir) ->
+ dict:is_key(Dir, Config#config.skip_dirs).
+
+reset_skip_dirs(Config) ->
+ Config#config{skip_dirs = new_skip_dirs()}.
+
+set_xconf(Config, Key, Value) ->
+ NewXconf = dict:store(Key, Value, Config#config.xconf),
+ Config#config{xconf=NewXconf}.
+
+get_xconf(Config, Key) ->
+ {ok, Value} = dict:find(Key, Config#config.xconf),
+ Value.
+
+get_xconf(Config, Key, Default) ->
+ case dict:find(Key, Config#config.xconf) of
+ error ->
+ Default;
+ {ok, Value} ->
+ Value
+ end.
+
+erase_xconf(Config, Key) ->
+ NewXconf = dict:erase(Key, Config#config.xconf),
+ Config#config{xconf = NewXconf}.
+
+%% TODO: reconsider after config inheritance removal/redesign
+clean_config(Old, New) ->
+ New#config{opts=Old#config.opts}.
+
%% ===================================================================
%% Internal functions
%% ===================================================================
+new(ParentConfig, ConfName) ->
+ %% Load terms from rebar.config, if it exists
+ Dir = rebar_utils:get_cwd(),
+ ConfigFile = filename:join([Dir, ConfName]),
+ Opts0 = ParentConfig#config.opts,
+ Opts = case consult_file(ConfigFile) of
+ {ok, Terms} ->
+ %% Found a config file with some terms. We need to
+ %% be able to distinguish between local definitions
+ %% (i.e. from the file in the cwd) and inherited
+ %% definitions. To accomplish this, we use a marker
+ %% in the proplist (since order matters) between
+ %% the new and old defs.
+ Terms ++ [local] ++
+ [Opt || Opt <- Opts0, Opt /= local];
+ {error, enoent} ->
+ [local] ++
+ [Opt || Opt <- Opts0, Opt /= local];
+ Other ->
+ ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other])
+ end,
+
+ ParentConfig#config{dir = Dir, opts = Opts}.
+
consult_and_eval(File, Script) ->
?DEBUG("Evaluating config script ~p~n", [Script]),
ConfigData = try_consult(File),
file:script(Script, bs([{'CONFIG', ConfigData}, {'SCRIPT', Script}])).
-
remove_script_ext(F) ->
"tpircs." ++ Rev = lists:reverse(F),
lists:reverse(Rev).
@@ -171,7 +222,8 @@ try_consult(File) ->
{ok, Terms} ->
?DEBUG("Consult config file ~p~n", [File]),
Terms;
- {error, enoent} -> [];
+ {error, enoent} ->
+ [];
{error, Reason} ->
?ABORT("Failed to read config file ~s: ~p~n", [File, Reason])
end.
@@ -188,5 +240,10 @@ local_opts([local | _Rest], Acc) ->
local_opts([Item | Rest], Acc) ->
local_opts(Rest, [Item | Acc]).
-new_env() ->
- dict:new().
+new_globals() -> dict:new().
+
+new_env() -> dict:new().
+
+new_skip_dirs() -> dict:new().
+
+new_xconf() -> dict:new().
diff --git a/src/rebar_core.erl b/src/rebar_core.erl
index 99d3c38..9e3f9f0 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -26,97 +26,71 @@
%% -------------------------------------------------------------------
-module(rebar_core).
--export([process_commands/2,
- skip_dir/1,
- is_skip_dir/1,
- skip_dirs/0]).
+-export([process_commands/2]).
-include("rebar.hrl").
-
-%% ===================================================================
-%% Public API
-%% ===================================================================
-
-skip_dir(Dir) ->
- SkipDir = {skip_dir, Dir},
- case erlang:get(SkipDir) of
- undefined ->
- ?DEBUG("Adding skip dir: ~s\n", [Dir]),
- erlang:put(SkipDir, true);
- true ->
- ok
- end.
-
-is_skip_dir(Dir) ->
- case erlang:get({skip_dir, Dir}) of
- undefined ->
- false;
- true ->
- true
- end.
-
-skip_dirs() ->
- [Dir || {{skip_dir, Dir}, true} <- erlang:get()].
-
%% ===================================================================
%% Internal functions
%% ===================================================================
-process_commands([], _ParentConfig) ->
- AbortTrapped = rebar_config:get_global(abort_trapped, false),
- case {erlang:get(operations), AbortTrapped} of
+process_commands([], ParentConfig) ->
+ AbortTrapped = rebar_config:get_xconf(ParentConfig, abort_trapped, false),
+ case {get_operations(ParentConfig), AbortTrapped} of
{0, _} ->
%% None of the commands had any effect
- ?ABORT;
+ ?FAIL;
{_, true} ->
%% An abort was previously trapped
- ?ABORT;
+ ?FAIL;
_ ->
ok
end;
process_commands([Command | Rest], ParentConfig) ->
- try
- %% Reset skip dirs
- lists:foreach(fun (D) -> erlang:erase({skip_dir, D}) end, skip_dirs()),
- Operations = erlang:get(operations),
-
- %% Convert the code path so that all the entries are absolute paths.
- %% If not, code:set_path() may choke on invalid relative paths when
- %% trying to restore the code path from inside a subdirectory.
- true = rebar_utils:expand_code_path(),
- _ = process_dir(rebar_utils:get_cwd(), ParentConfig,
- Command, sets:new()),
- case erlang:get(operations) of
- Operations ->
- %% This command didn't do anything
- ?CONSOLE("Command '~p' not understood or not applicable~n",
- [Command]);
- _ ->
- ok
- end,
- %% Wipe out vsn cache to avoid invalid hits when
- %% dependencies are updated
- ets:delete_all_objects(rebar_vsn_cache)
- catch
- throw:rebar_abort ->
- case rebar_config:get_global(keep_going, false) of
- false ->
- ?ABORT;
- true ->
- ?WARN("Continuing on after abort: ~p\n", [Rest]),
- rebar_config:set_global(abort_trapped, true),
+ %% Reset skip dirs
+ ParentConfig1 = rebar_config:reset_skip_dirs(ParentConfig),
+ Operations = get_operations(ParentConfig1),
+
+ ParentConfig4 =
+ try
+ %% Convert the code path so that all the entries are absolute paths.
+ %% If not, code:set_path() may choke on invalid relative paths when trying
+ %% to restore the code path from inside a subdirectory.
+ true = rebar_utils:expand_code_path(),
+ {ParentConfig2, _DirSet} = process_dir(rebar_utils:get_cwd(),
+ ParentConfig1, Command,
+ sets:new()),
+ case get_operations(ParentConfig2) of
+ Operations ->
+ %% This command didn't do anything
+ ?CONSOLE("Command '~p' not understood or not applicable~n",
+ [Command]);
+ _ ->
ok
- end
- end,
- process_commands(Rest, ParentConfig).
-
+ end,
+ %% TODO: reconsider after config inheritance removal/redesign
+ ParentConfig3 = rebar_config:clean_config(ParentConfig1, ParentConfig2),
+ %% Wipe out vsn cache to avoid invalid hits when
+ %% dependencies are updated
+ rebar_config:set_xconf(ParentConfig3, vsn_cache, dict:new())
+ catch
+ throw:rebar_abort ->
+ case rebar_config:get_xconf(ParentConfig1, keep_going, false) of
+ false ->
+ ?FAIL;
+ true ->
+ ?WARN("Continuing on after abort: ~p\n", [Rest]),
+ rebar_config:set_xconf(ParentConfig1,
+ abort_trapped, true)
+ end
+ end,
+ process_commands(Rest, ParentConfig4).
process_dir(Dir, ParentConfig, Command, DirSet) ->
case filelib:is_dir(Dir) of
false ->
?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]),
- DirSet;
+ {ParentConfig, DirSet};
true ->
ok = file:set_cwd(Dir),
@@ -157,13 +131,13 @@ maybe_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
maybe_process_dir0(AppFile, ModuleSet, Config, CurrentCodePath,
Dir, Command, DirSet) ->
- case rebar_app_utils:is_skipped_app(AppFile) of
- {true, SkippedApp} ->
+ case rebar_app_utils:is_skipped_app(Config, AppFile) of
+ {Config1, {true, SkippedApp}} ->
?DEBUG("Skipping app: ~p~n", [SkippedApp]),
- increment_operations(),
- DirSet;
- false ->
- process_dir0(Dir, Command, DirSet, Config,
+ Config2 = increment_operations(Config1),
+ {Config2, DirSet};
+ {Config1, false} ->
+ process_dir0(Dir, Command, DirSet, Config1,
CurrentCodePath, ModuleSet)
end.
@@ -179,64 +153,63 @@ process_dir0(Dir, Command, DirSet, Config0, CurrentCodePath,
%% Invoke 'preprocess' on the modules -- this yields a list of other
%% directories that should be processed _before_ the current one.
- Predirs = acc_modules(Modules, preprocess, Config0, ModuleSetFile),
+ {Config1, Predirs} = acc_modules(Modules, preprocess, Config0,
+ ModuleSetFile),
SubdirAssoc = remember_cwd_subdir(Dir, Predirs),
%% Get the list of plug-in modules from rebar.config. These
%% modules may participate in preprocess and postprocess.
- {ok, PluginModules} = plugin_modules(Config0, SubdirAssoc),
+ {ok, PluginModules} = plugin_modules(Config1, SubdirAssoc),
- PluginPredirs = acc_modules(PluginModules, preprocess,
- Config0, ModuleSetFile),
+ {Config2, PluginPredirs} = acc_modules(PluginModules, preprocess,
+ Config1, ModuleSetFile),
AllPredirs = Predirs ++ PluginPredirs,
?DEBUG("Predirs: ~p\n", [AllPredirs]),
- DirSet2 = process_each(AllPredirs, Command, Config0,
- ModuleSetFile, DirSet),
+ {Config3, DirSet2} = process_each(AllPredirs, Command, Config2,
+ ModuleSetFile, DirSet),
%% Make sure the CWD is reset properly; processing the dirs may have
%% caused it to change
ok = file:set_cwd(Dir),
%% Check that this directory is not on the skip list
- Config = case is_skip_dir(Dir) of
- true ->
- %% Do not execute the command on the directory, as some
- %% module has requested a skip on it.
- ?INFO("Skipping ~s in ~s\n", [Command, Dir]),
- Config0;
-
- false ->
- %% Check for and get command specific environments
- {Config1, Env} = setup_envs(Config0, Modules),
-
- %% Execute any before_command plugins on this directory
- execute_pre(Command, PluginModules,
- Config1, ModuleSetFile, Env),
-
- %% Execute the current command on this directory
- execute(Command, Modules ++ PluginModules,
- Config1, ModuleSetFile, Env),
-
- %% Execute any after_command plugins on this directory
- execute_post(Command, PluginModules,
- Config1, ModuleSetFile, Env),
-
- Config1
- end,
+ Config7 = case rebar_config:is_skip_dir(Config3, Dir) of
+ true ->
+ %% Do not execute the command on the directory, as some
+ %% module has requested a skip on it.
+ ?INFO("Skipping ~s in ~s\n", [Command, Dir]),
+ Config3;
+
+ false ->
+ %% Check for and get command specific environments
+ {Config4, Env} = setup_envs(Config3, Modules),
+
+ %% Execute any before_command plugins on this directory
+ Config5 = execute_pre(Command, PluginModules,
+ Config4, ModuleSetFile, Env),
+
+ %% Execute the current command on this directory
+ Config6 = execute(Command, Modules ++ PluginModules,
+ Config5, ModuleSetFile, Env),
+
+ %% Execute any after_command plugins on this directory
+ execute_post(Command, PluginModules,
+ Config6, ModuleSetFile, Env)
+ end,
%% Mark the current directory as processed
DirSet3 = sets:add_element(Dir, DirSet2),
%% Invoke 'postprocess' on the modules. This yields a list of other
%% directories that should be processed _after_ the current one.
- Postdirs = acc_modules(Modules ++ PluginModules, postprocess,
- Config, ModuleSetFile),
+ {Config8, Postdirs} = acc_modules(Modules ++ PluginModules, postprocess,
+ Config7, ModuleSetFile),
?DEBUG("Postdirs: ~p\n", [Postdirs]),
- DirSet4 = process_each(Postdirs, Command, Config,
- ModuleSetFile, DirSet3),
+ Res = process_each(Postdirs, Command, Config8,
+ ModuleSetFile, DirSet3),
%% Make sure the CWD is reset properly; processing the dirs may have
%% caused it to change
@@ -246,8 +219,8 @@ process_dir0(Dir, Command, DirSet, Config0, CurrentCodePath,
%% the parent initialized it to
restore_code_path(CurrentCodePath),
- %% Return the updated dirset as our result
- DirSet4.
+ %% Return the updated {config, dirset} as result
+ Res.
remember_cwd_subdir(Cwd, Subdirs) ->
Store = fun(Dir, Dict) ->
@@ -269,30 +242,32 @@ remember_cwd_subdir(Cwd, Subdirs) ->
maybe_load_local_config(Dir, ParentConfig) ->
%% We need to ensure we don't overwrite custom
%% config when we are dealing with base_dir.
- case processing_base_dir(Dir) of
+ case rebar_utils:processing_base_dir(ParentConfig, Dir) of
true ->
ParentConfig;
false ->
rebar_config:new(ParentConfig)
end.
-processing_base_dir(Dir) ->
- Dir == rebar_config:get_global(base_dir, undefined).
-
%%
%% Given a list of directories and a set of previously processed directories,
%% process each one we haven't seen yet
%%
-process_each([], _Command, _Config, _ModuleSetFile, DirSet) ->
- DirSet;
+process_each([], _Command, Config, _ModuleSetFile, DirSet) ->
+ %% reset cached (setup_env) envs
+ Config1 = rebar_config:reset_envs(Config),
+ {Config1, DirSet};
process_each([Dir | Rest], Command, Config, ModuleSetFile, DirSet) ->
case sets:is_element(Dir, DirSet) of
true ->
?DEBUG("Skipping ~s; already processed!\n", [Dir]),
process_each(Rest, Command, Config, ModuleSetFile, DirSet);
false ->
- DirSet2 = process_dir(Dir, Config, Command, DirSet),
- process_each(Rest, Command, Config, ModuleSetFile, DirSet2)
+ {Config1, DirSet2} = process_dir(Dir, Config, Command, DirSet),
+ Config2 = rebar_config:clean_config(Config, Config1),
+ %% reset cached (setup_env) envs
+ Config3 = rebar_config:reset_envs(Config2),
+ process_each(Rest, Command, Config3, ModuleSetFile, DirSet2)
end.
@@ -343,24 +318,25 @@ execute(Command, Modules, Config, ModuleFile, Env) ->
false ->
?WARN("'~p' command does not apply to directory ~s\n",
[Command, rebar_utils:get_cwd()])
- end;
+ end,
+ Config;
TargetModules ->
%% Provide some info on where we are
Dir = rebar_utils:get_cwd(),
?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]),
- increment_operations(),
+ Config1 = increment_operations(Config),
%% Run the available modules
- apply_hooks(pre_hooks, Config, Command, Env),
+ apply_hooks(pre_hooks, Config1, Command, Env),
case catch(run_modules(TargetModules, Command,
- Config, ModuleFile)) of
- ok ->
- apply_hooks(post_hooks, Config, Command, Env),
- ok;
+ Config1, ModuleFile)) of
+ {ok, NewConfig} ->
+ apply_hooks(post_hooks, NewConfig, Command, Env),
+ NewConfig;
{error, failed} ->
- ?ABORT;
+ ?FAIL;
{Module, {error, _} = Other} ->
?ABORT("~p failed while processing ~s in module ~s: ~s\n",
[Command, Dir, Module,
@@ -373,9 +349,12 @@ execute(Command, Modules, Config, ModuleFile, Env) ->
%% Increment the count of operations, since some module
%% responds to this command
-increment_operations() ->
- erlang:put(operations, erlang:get(operations) + 1).
+increment_operations(Config) ->
+ Operations = get_operations(Config),
+ rebar_config:set_xconf(Config, operations, Operations + 1).
+get_operations(Config) ->
+ rebar_config:get_xconf(Config, operations).
update_code_path(Config) ->
case rebar_config:get_local(Config, lib_dirs, []) of
@@ -393,9 +372,11 @@ restore_code_path(no_change) ->
restore_code_path({old, Path}) ->
%% Verify that all of the paths still exist -- some dynamically
%% added paths can get blown away during clean.
- true = code:set_path([F || F <- Path, filelib:is_file(F)]),
+ true = code:set_path([F || F <- Path, erl_prim_loader_is_file(F)]),
ok.
+erl_prim_loader_is_file(File) ->
+ erl_prim_loader:read_file_info(File) =/= error.
expand_lib_dirs([], _Root, Acc) ->
Acc;
@@ -417,12 +398,14 @@ select_modules([Module | Rest], Command, Acc) ->
select_modules(Rest, Command, Acc)
end.
-run_modules([], _Command, _Config, _File) ->
- ok;
+run_modules([], _Command, Config, _File) ->
+ {ok, Config};
run_modules([Module | Rest], Command, Config, File) ->
case Module:Command(Config, File) of
ok ->
run_modules(Rest, Command, Config, File);
+ {ok, NewConfig} ->
+ run_modules(Rest, Command, NewConfig, File);
{error, _} = Error ->
{Module, Error}
end.
@@ -450,7 +433,7 @@ setup_envs(Config, Modules) ->
case erlang:function_exported(M, setup_env, 1) of
true ->
Env = M:setup_env(C),
- C1 = rebar_config:set_env(C, M, Env),
+ C1 = rebar_config:save_env(C, M, Env),
{C1, E++Env};
false ->
T
@@ -461,11 +444,16 @@ acc_modules(Modules, Command, Config, File) ->
acc_modules(select_modules(Modules, Command, []),
Command, Config, File, []).
-acc_modules([], _Command, _Config, _File, Acc) ->
- Acc;
+acc_modules([], _Command, Config, _File, Acc) ->
+ {Config, Acc};
acc_modules([Module | Rest], Command, Config, File, Acc) ->
- {ok, Dirs} = Module:Command(Config, File),
- acc_modules(Rest, Command, Config, File, Acc ++ Dirs).
+ {Config1, Dirs1} = case Module:Command(Config, File) of
+ {ok, Dirs} ->
+ {Config, Dirs};
+ {ok, NewConfig, Dirs} ->
+ {NewConfig, Dirs}
+ end,
+ acc_modules(Rest, Command, Config1, File, Acc ++ Dirs1).
%%
%% Return a flat list of rebar plugin modules.
diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl
index 57f038a..6fd5bc7 100644
--- a/src/rebar_ct.erl
+++ b/src/rebar_ct.erl
@@ -47,48 +47,56 @@
ct(Config, File) ->
TestDir = rebar_config:get_local(Config, ct_dir, "test"),
- run_test_if_present(TestDir, Config, File).
+ LogDir = rebar_config:get_local(Config, ct_log_dir, "logs"),
+ run_test_if_present(TestDir, LogDir, Config, File).
%% ===================================================================
%% Internal functions
%% ===================================================================
-run_test_if_present(TestDir, Config, File) ->
+run_test_if_present(TestDir, LogDir, Config, File) ->
case filelib:is_dir(TestDir) of
false ->
?WARN("~s directory not present - skipping\n", [TestDir]),
ok;
true ->
- run_test(TestDir, Config, File)
+ case filelib:wildcard(TestDir ++ "/*_SUITE.{beam,erl}") of
+ [] ->
+ ?WARN("~s directory present, but no common_test"
+ ++ " SUITES - skipping\n", [TestDir]),
+ ok;
+ _ ->
+ run_test(TestDir, LogDir, Config, File)
+ end
end.
-run_test(TestDir, Config, _File) ->
- {Cmd, RawLog} = make_cmd(TestDir, Config),
- clear_log(RawLog),
- case rebar_config:is_verbose() of
- false ->
- Output = " >> " ++ RawLog ++ " 2>&1";
- true ->
- Output = " 2>&1 | tee -a " ++ RawLog
- end,
+run_test(TestDir, LogDir, Config, _File) ->
+ {Cmd, RawLog} = make_cmd(TestDir, LogDir, Config),
+ ?DEBUG("ct_run cmd:~n~p~n", [Cmd]),
+ clear_log(LogDir, RawLog),
+ Output = case rebar_config:is_verbose(Config) of
+ false ->
+ " >> " ++ RawLog ++ " 2>&1";
+ true ->
+ " 2>&1 | tee -a " ++ RawLog
+ end,
rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]),
- check_log(RawLog).
-
+ check_log(Config, RawLog).
-clear_log(RawLog) ->
- case filelib:ensure_dir("logs/index.html") of
+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]),
- ?ABORT
+ ?FAIL
end.
%% calling ct with erl does not return non-zero on failure - have to check
%% log results
-check_log(RawLog) ->
+check_log(Config, RawLog) ->
{ok, Msg} =
rebar_utils:sh("grep -e 'TEST COMPLETE' -e '{error,make_failed}' "
++ RawLog, [{use_stdout, false}]),
@@ -96,23 +104,23 @@ check_log(RawLog) ->
RunFailed = string:str(Msg, ", 0 failed") =:= 0,
if
MakeFailed ->
- show_log(RawLog),
+ show_log(Config, RawLog),
?ERROR("Building tests failed\n",[]),
- ?ABORT;
+ ?FAIL;
RunFailed ->
- show_log(RawLog),
+ show_log(Config, RawLog),
?ERROR("One or more tests failed\n",[]),
- ?ABORT;
+ ?FAIL;
true ->
?CONSOLE("DONE.\n~s\n", [Msg])
end.
%% Show the log if it hasn't already been shown because verbose was on
-show_log(RawLog) ->
+show_log(Config, RawLog) ->
?CONSOLE("Showing log\n", []),
- case rebar_config:is_verbose() of
+ case rebar_config:is_verbose(Config) of
false ->
{ok, Contents} = file:read_file(RawLog),
?CONSOLE("~s", [Contents]);
@@ -120,9 +128,9 @@ show_log(RawLog) ->
ok
end.
-make_cmd(TestDir, Config) ->
+make_cmd(TestDir, RawLogDir, Config) ->
Cwd = rebar_utils:get_cwd(),
- LogDir = filename:join(Cwd, "logs"),
+ LogDir = filename:join(Cwd, RawLogDir),
EbinDir = filename:absname(filename:join(Cwd, "ebin")),
IncludeDir = filename:join(Cwd, "include"),
Include = case filelib:is_dir(IncludeDir) of
@@ -159,8 +167,8 @@ make_cmd(TestDir, Config) ->
get_cover_config(Config, Cwd) ++
get_ct_config_file(TestDir) ++
get_config_file(TestDir) ++
- get_suites(TestDir) ++
- get_case();
+ get_suites(Config, TestDir) ++
+ get_case(Config);
SpecFlags ->
?FMT("erl " % should we expand ERL_PATH?
" -noshell -pa ~s ~s"
@@ -248,8 +256,8 @@ get_config_file(TestDir) ->
" -config " ++ Config
end.
-get_suites(TestDir) ->
- case rebar_utils:get_deprecated_global(suite, suites, "soon") of
+get_suites(Config, TestDir) ->
+ case rebar_config:get_global(Config, suites, undefined) of
undefined ->
" -dir " ++ TestDir;
Suites ->
@@ -263,13 +271,13 @@ find_suite_path(Suite, TestDir) ->
case filelib:is_regular(Path) of
false ->
?ERROR("Suite ~s not found\n", [Suite]),
- ?ABORT;
+ ?FAIL;
true ->
Path
end.
-get_case() ->
- case rebar_config:get_global('case', undefined) of
+get_case(Config) ->
+ case rebar_config:get_global(Config, 'case', undefined) of
undefined ->
"";
Case ->
diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl
index dc2fe84..cd49343 100644
--- a/src/rebar_deps.erl
+++ b/src/rebar_deps.erl
@@ -42,7 +42,8 @@
-record(dep, { dir,
app,
vsn_regex,
- source }).
+ source,
+ is_raw }). %% is_raw = true means non-Erlang/OTP dependency
%% ===================================================================
%% Public API
@@ -52,50 +53,55 @@ preprocess(Config, _) ->
%% Side effect to set deps_dir globally for all dependencies from
%% top level down. Means the root deps_dir is honoured or the default
%% used globally since it will be set on the first time through here
- set_global_deps_dir(Config, rebar_config:get_global(deps_dir, [])),
+ Config1 = set_shared_deps_dir(Config, get_shared_deps_dir(Config, [])),
%% Get the list of deps for the current working directory and identify those
%% deps that are available/present.
- Deps = rebar_config:get_local(Config, deps, []),
- {AvailableDeps, MissingDeps} = find_deps(find, Deps),
+ Deps = rebar_config:get_local(Config1, deps, []),
+ {Config2, {AvailableDeps, MissingDeps}} = find_deps(Config1, find, Deps),
?DEBUG("Available deps: ~p\n", [AvailableDeps]),
?DEBUG("Missing deps : ~p\n", [MissingDeps]),
%% Add available deps to code path
- update_deps_code_path(AvailableDeps),
+ Config3 = update_deps_code_path(Config2, AvailableDeps),
%% If skip_deps=true, mark each dep dir as a skip_dir w/ the core so that
%% the current command doesn't run on the dep dir. However, pre/postprocess
%% WILL run (and we want it to) for transitivity purposes.
- case rebar_config:get_global(skip_deps, false) of
- "true" ->
- lists:foreach(fun (#dep{dir = Dir}) ->
- rebar_core:skip_dir(Dir)
- end, AvailableDeps);
- _ ->
- ok
- end,
+ NewConfig = case rebar_config:get_global(Config3, skip_deps, false) of
+ "true" ->
+ lists:foldl(
+ fun(#dep{dir = Dir}, C) ->
+ rebar_config:set_skip_dir(C, Dir)
+ end, Config3, AvailableDeps);
+ _ ->
+ Config3
+ end,
- %% Return all the available dep directories for process
- {ok, [D#dep.dir || D <- AvailableDeps]}.
+ %% Filtering out 'raw' dependencies so that no commands other than
+ %% deps-related can be executed on their directories.
+ NonRawAvailableDeps = [D || D <- AvailableDeps, not D#dep.is_raw],
+ %% Return all the available dep directories for process
+ {ok, NewConfig, dep_dirs(NonRawAvailableDeps)}.
-postprocess(_Config, _) ->
- case erlang:get(?MODULE) of
+postprocess(Config, _) ->
+ case rebar_config:get_xconf(Config, ?MODULE, undefined) of
undefined ->
{ok, []};
Dirs ->
- erlang:erase(?MODULE),
- {ok, Dirs}
+ NewConfig = rebar_config:erase_xconf(Config, ?MODULE),
+ {ok, NewConfig, Dirs}
end.
-compile(Config, AppFile) ->
- 'check-deps'(Config, AppFile).
+compile(Config, _) ->
+ {Config1, _AvailDeps} = do_check_deps(Config),
+ {ok, Config1}.
%% set REBAR_DEPS_DIR and ERL_LIBS environment variables
-setup_env(_Config) ->
- {true, DepsDir} = get_deps_dir(),
+setup_env(Config) ->
+ {true, DepsDir} = get_deps_dir(Config),
%% include rebar's DepsDir in ERL_LIBS
Separator = case os:type() of
{win32, nt} ->
@@ -111,65 +117,76 @@ setup_env(_Config) ->
end,
[{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS].
-'check-deps'(Config, _) ->
+%% common function used by 'check-deps' and 'compile'
+do_check_deps(Config) ->
%% Get the list of immediate (i.e. non-transitive) deps that are missing
Deps = rebar_config:get_local(Config, deps, []),
- case find_deps(find, Deps) of
- {_, []} ->
+ case find_deps(Config, find, Deps) of
+ {Config1, {AvailDeps, []}} ->
%% No missing deps
- ok;
- {_, MissingDeps} ->
+ {Config1, AvailDeps};
+ {_Config1, {_, MissingDeps}} ->
lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) ->
?CONSOLE("Dependency not available: "
"~p-~s (~p)\n", [App, Vsn, Src])
end, MissingDeps),
- ?ABORT
+ ?FAIL
end.
+'check-deps'(Config, _) ->
+ {Config1, AvailDeps} = do_check_deps(Config),
+ {ok, save_dep_dirs(Config1, AvailDeps)}.
+
'get-deps'(Config, _) ->
%% Determine what deps are available and missing
Deps = rebar_config:get_local(Config, deps, []),
- {_AvailableDeps, MissingDeps} = find_deps(find, Deps),
+ {Config1, {_AvailableDeps, MissingDeps}} = find_deps(Config, find, Deps),
+ MissingDeps1 = [D || D <- MissingDeps, D#dep.source =/= undefined],
%% For each missing dep with a specified source, try to pull it.
- PulledDeps = [use_source(D) || D <- MissingDeps, D#dep.source /= undefined],
+ {Config2, PulledDeps} =
+ lists:foldl(fun(D, {C, PulledDeps0}) ->
+ {C1, D1} = use_source(C, D),
+ {C1, [D1 | PulledDeps0]}
+ end, {Config1, []}, MissingDeps1),
%% Add each pulled dep to our list of dirs for post-processing. This yields
%% the necessary transitivity of the deps
- erlang:put(?MODULE, [D#dep.dir || D <- PulledDeps]),
- ok.
+ {ok, save_dep_dirs(Config2, lists:reverse(PulledDeps))}.
'update-deps'(Config, _) ->
- %% Determine what deps are available and missing
- Deps = rebar_config:get_local(Config, deps, []),
- UpdatedDeps = [update_source(D) || D <- find_deps(read, Deps),
- D#dep.source /= undefined],
+ %% Determine what deps are required
+ RawDeps = rebar_config:get_local(Config, deps, []),
+ {Config1, Deps} = find_deps(Config, read, RawDeps),
+
+ %% Update each dep
+ UpdatedDeps = [update_source(Config1, D)
+ || D <- Deps, D#dep.source =/= undefined],
+
%% Add each updated dep to our list of dirs for post-processing. This yields
%% the necessary transitivity of the deps
- erlang:put(?MODULE, [D#dep.dir || D <- UpdatedDeps]),
- ok.
+ {ok, save_dep_dirs(Config1, UpdatedDeps)}.
'delete-deps'(Config, _) ->
%% Delete all the available deps in our deps/ directory, if any
- {true, DepsDir} = get_deps_dir(),
+ {true, DepsDir} = get_deps_dir(Config),
Deps = rebar_config:get_local(Config, deps, []),
- {AvailableDeps, _} = find_deps(find, Deps),
+ {Config1, {AvailableDeps, _}} = find_deps(Config, find, Deps),
_ = [delete_dep(D)
|| D <- AvailableDeps,
lists:prefix(DepsDir, D#dep.dir)],
- ok.
+ {ok, Config1}.
'list-deps'(Config, _) ->
Deps = rebar_config:get_local(Config, deps, []),
- case find_deps(find, Deps) of
- {AvailDeps, []} ->
+ case find_deps(Config, find, Deps) of
+ {Config1, {AvailDeps, []}} ->
lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps),
- ok;
+ {ok, save_dep_dirs(Config1, AvailDeps)};
{_, MissingDeps} ->
?ABORT("Missing dependencies: ~p\n", [MissingDeps])
end.
-
%% ===================================================================
%% Internal functions
%% ===================================================================
@@ -177,19 +194,29 @@ setup_env(_Config) ->
%% Added because of trans deps,
%% need all deps in same dir and should be the one set by the root rebar.config
%% Sets a default if root config has no deps_dir set
-set_global_deps_dir(Config, []) ->
- rebar_config:set_global(deps_dir,
- rebar_config:get_local(Config, deps_dir, "deps"));
-set_global_deps_dir(_Config, _DepsDir) ->
- ok.
+set_shared_deps_dir(Config, []) ->
+ GlobalDepsDir = rebar_config:get_global(Config, deps_dir, "deps"),
+ DepsDir = rebar_config:get_local(Config, deps_dir, GlobalDepsDir),
+ rebar_config:set_xconf(Config, deps_dir, DepsDir);
+set_shared_deps_dir(Config, _DepsDir) ->
+ Config.
+
+get_shared_deps_dir(Config, Default) ->
+ rebar_config:get_xconf(Config, deps_dir, Default).
+
+get_deps_dir(Config) ->
+ get_deps_dir(Config, "").
+
+get_deps_dir(Config, App) ->
+ BaseDir = rebar_config:get_xconf(Config, base_dir, []),
+ DepsDir = get_shared_deps_dir(Config, "deps"),
+ {true, filename:join([BaseDir, DepsDir, App])}.
-get_deps_dir() ->
- get_deps_dir("").
+dep_dirs(Deps) ->
+ [D#dep.dir || D <- Deps].
-get_deps_dir(App) ->
- BaseDir = rebar_config:get_global(base_dir, []),
- DepsDir = rebar_config:get_global(deps_dir, "deps"),
- {true, filename:join([BaseDir, DepsDir, App])}.
+save_dep_dirs(Config, Deps) ->
+ rebar_config:set_xconf(Config, ?MODULE, dep_dirs(Deps)).
get_lib_dir(App) ->
%% Find App amongst the reachable lib directories
@@ -200,72 +227,83 @@ get_lib_dir(App) ->
Path -> {true, Path}
end.
-update_deps_code_path([]) ->
- ok;
-update_deps_code_path([Dep | Rest]) ->
- case is_app_available(Dep#dep.app, Dep#dep.vsn_regex, Dep#dep.dir) of
- {true, _} ->
- Dir = filename:join(Dep#dep.dir, "ebin"),
- ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
- ?DEBUG("Adding ~s to code path~n", [Dir]),
- true = code:add_patha(Dir);
- {false, _} ->
- true
- end,
- update_deps_code_path(Rest).
-
-
-find_deps(find=Mode, Deps) ->
- find_deps(Mode, Deps, {[], []});
-find_deps(read=Mode, Deps) ->
- find_deps(Mode, Deps, []).
-
-find_deps(find, [], {Avail, Missing}) ->
- {lists:reverse(Avail), lists:reverse(Missing)};
-find_deps(read, [], Deps) ->
- lists:reverse(Deps);
-find_deps(Mode, [App | Rest], Acc) when is_atom(App) ->
- find_deps(Mode, [{App, ".*", undefined} | Rest], Acc);
-find_deps(Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
- find_deps(Mode, [{App, VsnRegex, undefined} | Rest], Acc);
-find_deps(Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
+update_deps_code_path(Config, []) ->
+ Config;
+update_deps_code_path(Config, [Dep | Rest]) ->
+ Config2 =
+ case is_app_available(Config, Dep#dep.app,
+ Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
+ {Config1, {true, _}} ->
+ Dir = filename:join(Dep#dep.dir, "ebin"),
+ ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
+ ?DEBUG("Adding ~s to code path~n", [Dir]),
+ true = code:add_patha(Dir),
+ Config1;
+ {Config1, {false, _}} ->
+ Config1
+ end,
+ update_deps_code_path(Config2, Rest).
+
+find_deps(Config, find=Mode, Deps) ->
+ find_deps(Config, Mode, Deps, {[], []});
+find_deps(Config, read=Mode, Deps) ->
+ find_deps(Config, Mode, Deps, []).
+
+find_deps(Config, find, [], {Avail, Missing}) ->
+ {Config, {lists:reverse(Avail), lists:reverse(Missing)}};
+find_deps(Config, read, [], Deps) ->
+ {Config, lists:reverse(Deps)};
+find_deps(Config, Mode, [App | Rest], Acc) when is_atom(App) ->
+ find_deps(Config, Mode, [{App, ".*", undefined} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
+ find_deps(Config, Mode, [{App, VsnRegex, undefined} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
+ find_deps(Config, Mode, [{App, VsnRegex, Source, []} | Rest], Acc);
+find_deps(Config, Mode, [{App, VsnRegex, Source, Opts} | Rest], Acc) when is_list(Opts) ->
Dep = #dep { app = App,
vsn_regex = VsnRegex,
- source = Source },
- {Availability, FoundDir} = find_dep(Dep),
- find_deps(Mode, Rest, acc_deps(Mode, Availability, Dep, FoundDir, Acc));
-find_deps(_Mode, [Other | _Rest], _Acc) ->
+ source = Source,
+ %% dependency is considered raw (i.e. non-Erlang/OTP) when
+ %% 'raw' option is present
+ is_raw = proplists:get_value(raw, Opts, false) },
+ {Config1, {Availability, FoundDir}} = find_dep(Config, Dep),
+ find_deps(Config1, Mode, Rest,
+ acc_deps(Mode, Availability, Dep, FoundDir, Acc));
+find_deps(_Config, _Mode, [Other | _Rest], _Acc) ->
?ABORT("Invalid dependency specification ~p in ~s\n",
[Other, rebar_utils:get_cwd()]).
-find_dep(Dep) ->
+find_dep(Config, Dep) ->
%% Find a dep based on its source,
%% e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"}
%% Deps with a source must be found (or fetched) locally.
%% Those without a source may be satisfied from lib dir (get_lib_dir).
- find_dep(Dep, Dep#dep.source).
+ find_dep(Config, Dep, Dep#dep.source).
-find_dep(Dep, undefined) ->
+find_dep(Config, Dep, undefined) ->
%% 'source' is undefined. If Dep is not satisfied locally,
%% go ahead and find it amongst the lib_dir's.
- case find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)) of
- {avail, _Dir} = Avail -> Avail;
- {missing, _} -> find_dep_in_dir(Dep, get_lib_dir(Dep#dep.app))
+ case find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)) of
+ {_Config1, {avail, _Dir}} = Avail ->
+ Avail;
+ {Config1, {missing, _}} ->
+ find_dep_in_dir(Config1, Dep, get_lib_dir(Dep#dep.app))
end;
-find_dep(Dep, _Source) ->
+find_dep(Config, Dep, _Source) ->
%% _Source is defined. Regardless of what it is, we must find it
%% locally satisfied or fetch it from the original source
%% into the project's deps
- find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)).
+ find_dep_in_dir(Config, Dep, get_deps_dir(Config, Dep#dep.app)).
-find_dep_in_dir(_Dep, {false, Dir}) ->
- {missing, Dir};
-find_dep_in_dir(Dep, {true, Dir}) ->
+find_dep_in_dir(Config, _Dep, {false, Dir}) ->
+ {Config, {missing, Dir}};
+find_dep_in_dir(Config, Dep, {true, Dir}) ->
App = Dep#dep.app,
VsnRegex = Dep#dep.vsn_regex,
- case is_app_available(App, VsnRegex, Dir) of
- {true, _AppFile} -> {avail, Dir};
- {false, _} -> {missing, Dir}
+ IsRaw = Dep#dep.is_raw,
+ case is_app_available(Config, App, VsnRegex, Dir, IsRaw) of
+ {Config1, {true, _AppFile}} -> {Config1, {avail, Dir}};
+ {Config1, {false, _}} -> {Config1, {missing, Dir}}
end.
acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) ->
@@ -288,57 +326,76 @@ require_source_engine(Source) ->
true = source_engine_avail(Source),
ok.
-is_app_available(App, VsnRegex, Path) ->
+%% IsRaw = false means regular Erlang/OTP dependency
+%%
+%% IsRaw = true means non-Erlang/OTP dependency, e.g. the one that does not
+%% have a proper .app file
+is_app_available(Config, App, VsnRegex, Path, _IsRaw = false) ->
?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]),
case rebar_app_utils:is_app_dir(Path) of
{true, AppFile} ->
- case rebar_app_utils:app_name(AppFile) of
- App ->
- Vsn = rebar_app_utils:app_vsn(AppFile),
+ case rebar_app_utils:app_name(Config, AppFile) of
+ {Config1, App} ->
+ {Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppFile),
?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n",
[App, VsnRegex, App, Vsn, Path]),
case re:run(Vsn, VsnRegex, [{capture, none}]) of
match ->
- {true, Path};
+ {Config2, {true, Path}};
nomatch ->
?WARN("~s has version ~p; requested regex was ~s\n",
[AppFile, Vsn, VsnRegex]),
- {false, {version_mismatch,
- {AppFile,
- {expected, VsnRegex}, {has, Vsn}}}}
+ {Config2,
+ {false, {version_mismatch,
+ {AppFile,
+ {expected, VsnRegex}, {has, Vsn}}}}}
end;
- OtherApp ->
+ {Config1, OtherApp} ->
?WARN("~s has application id ~p; expected ~p\n",
[AppFile, OtherApp, App]),
- {false, {name_mismatch,
- {AppFile, {expected, App}, {has, OtherApp}}}}
+ {Config1,
+ {false, {name_mismatch,
+ {AppFile, {expected, App}, {has, OtherApp}}}}}
end;
false ->
?WARN("Expected ~s to be an app dir (containing ebin/*.app), "
"but no .app found.\n", [Path]),
- {false, {missing_app_file, Path}}
+ {Config, {false, {missing_app_file, Path}}}
+ end;
+is_app_available(Config, App, _VsnRegex, Path, _IsRaw = true) ->
+ ?DEBUG("is_app_available, looking for Raw Depencency ~p with Path ~p~n", [App, Path]),
+ case filelib:is_dir(Path) of
+ true ->
+ %% TODO: look for version string in <Path>/VERSION file? Not clear
+ %% how to detect git/svn/hg/{cmd, ...} settings that can be passed
+ %% to rebar_utils:vcs_vsn/2 to obtain version dynamically
+ {Config, {true, Path}};
+ false ->
+ ?WARN("Expected ~s to be a raw dependency directory, "
+ "but no directory found.\n", [Path]),
+ {Config, {false, {missing_raw_dependency_directory, Path}}}
end.
-use_source(Dep) ->
- use_source(Dep, 3).
+use_source(Config, Dep) ->
+ use_source(Config, Dep, 3).
-use_source(Dep, 0) ->
+use_source(_Config, Dep, 0) ->
?ABORT("Failed to acquire source from ~p after 3 tries.\n",
[Dep#dep.source]);
-use_source(Dep, Count) ->
+use_source(Config, Dep, Count) ->
case filelib:is_dir(Dep#dep.dir) of
true ->
%% Already downloaded -- verify the versioning matches the regex
- case is_app_available(Dep#dep.app,
- Dep#dep.vsn_regex, Dep#dep.dir) of
- {true, _} ->
+ case is_app_available(Config, Dep#dep.app,
+ Dep#dep.vsn_regex, Dep#dep.dir, Dep#dep.is_raw) of
+ {Config1, {true, _}} ->
Dir = filename:join(Dep#dep.dir, "ebin"),
ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
%% Available version matches up -- we're good to go;
%% add the app dir to our code path
true = code:add_patha(Dir),
- Dep;
- {false, Reason} ->
+ {Config1, Dep};
+ {_Config1, {false, Reason}} ->
%% The app that was downloaded doesn't match up (or had
%% errors or something). For the time being, abort.
?ABORT("Dependency dir ~s failed application validation "
@@ -347,9 +404,9 @@ use_source(Dep, Count) ->
false ->
?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
require_source_engine(Dep#dep.source),
- {true, TargetDir} = get_deps_dir(Dep#dep.app),
+ {true, TargetDir} = get_deps_dir(Config, Dep#dep.app),
download_source(TargetDir, Dep#dep.source),
- use_source(Dep#dep { dir = TargetDir }, Count-1)
+ use_source(Config, Dep#dep { dir = TargetDir }, Count-1)
end.
download_source(AppDir, {hg, Url, Rev}) ->
@@ -388,19 +445,31 @@ download_source(AppDir, {svn, Url, Rev}) ->
[{cd, filename:dirname(AppDir)}]);
download_source(AppDir, {rsync, Url}) ->
ok = filelib:ensure_dir(AppDir),
- rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []).
-
-update_source(Dep) ->
+ rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []);
+download_source(AppDir, {fossil, Url}) ->
+ download_source(AppDir, {fossil, Url, ""});
+download_source(AppDir, {fossil, Url, latest}) ->
+ download_source(AppDir, {fossil, Url, ""});
+download_source(AppDir, {fossil, Url, Version}) ->
+ Repository = filename:join(AppDir, filename:basename(AppDir) ++ ".fossil"),
+ ok = filelib:ensure_dir(Repository),
+ ok = file:set_cwd(AppDir),
+ rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]),
+ [{cd, AppDir}]),
+ rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]),
+ []).
+
+update_source(Config, Dep) ->
%% It's possible when updating a source, that a given dep does not have a
%% VCS directory, such as when a source archive is built of a project, with
%% all deps already downloaded/included. So, verify that the necessary VCS
%% directory exists before attempting to do the update.
- {true, AppDir} = get_deps_dir(Dep#dep.app),
+ {true, AppDir} = get_deps_dir(Config, Dep#dep.app),
case has_vcs_dir(element(1, Dep#dep.source), AppDir) of
true ->
?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
require_source_engine(Dep#dep.source),
- update_source(AppDir, Dep#dep.source),
+ update_source1(AppDir, Dep#dep.source),
Dep;
false ->
?WARN("Skipping update for ~p: "
@@ -408,32 +477,38 @@ update_source(Dep) ->
Dep
end.
-update_source(AppDir, {git, Url}) ->
- update_source(AppDir, {git, Url, {branch, "HEAD"}});
-update_source(AppDir, {git, Url, ""}) ->
- update_source(AppDir, {git, Url, {branch, "HEAD"}});
-update_source(AppDir, {git, _Url, {branch, Branch}}) ->
+update_source1(AppDir, {git, Url}) ->
+ update_source1(AppDir, {git, Url, {branch, "HEAD"}});
+update_source1(AppDir, {git, Url, ""}) ->
+ update_source1(AppDir, {git, Url, {branch, "HEAD"}});
+update_source1(AppDir, {git, _Url, {branch, Branch}}) ->
ShOpts = [{cd, AppDir}],
rebar_utils:sh("git fetch origin", ShOpts),
rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), ShOpts);
-update_source(AppDir, {git, _Url, {tag, Tag}}) ->
+update_source1(AppDir, {git, _Url, {tag, Tag}}) ->
ShOpts = [{cd, AppDir}],
rebar_utils:sh("git fetch --tags origin", ShOpts),
rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts);
-update_source(AppDir, {git, _Url, Refspec}) ->
+update_source1(AppDir, {git, _Url, Refspec}) ->
ShOpts = [{cd, AppDir}],
rebar_utils:sh("git fetch origin", ShOpts),
rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts);
-update_source(AppDir, {svn, _Url, Rev}) ->
+update_source1(AppDir, {svn, _Url, Rev}) ->
rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]);
-update_source(AppDir, {hg, _Url, Rev}) ->
+update_source1(AppDir, {hg, _Url, Rev}) ->
rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]);
-update_source(AppDir, {bzr, _Url, Rev}) ->
+update_source1(AppDir, {bzr, _Url, Rev}) ->
rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]);
-update_source(AppDir, {rsync, Url}) ->
- rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]).
-
-
+update_source1(AppDir, {rsync, Url}) ->
+ rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]);
+update_source1(AppDir, {fossil, Url}) ->
+ update_source1(AppDir, {fossil, Url, ""});
+update_source1(AppDir, {fossil, Url, latest}) ->
+ update_source1(AppDir, {fossil, Url, ""});
+update_source1(AppDir, {fossil, _Url, Version}) ->
+ ok = file:set_cwd(AppDir),
+ rebar_utils:sh("fossil pull", [{cd, AppDir}]),
+ rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).
%% ===================================================================
@@ -445,7 +520,8 @@ source_engine_avail(Source) ->
source_engine_avail(Name, Source).
source_engine_avail(Name, Source)
- when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync ->
+ when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync;
+ Name == fossil ->
case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of
true ->
true;
@@ -466,11 +542,12 @@ vcs_client_vsn(Path, VsnArg, VsnRegex) ->
false
end.
-required_vcs_client_vsn(hg) -> {1, 1};
-required_vcs_client_vsn(git) -> {1, 5};
-required_vcs_client_vsn(bzr) -> {2, 0};
-required_vcs_client_vsn(svn) -> {1, 6};
-required_vcs_client_vsn(rsync) -> {2, 0}.
+required_vcs_client_vsn(hg) -> {1, 1};
+required_vcs_client_vsn(git) -> {1, 5};
+required_vcs_client_vsn(bzr) -> {2, 0};
+required_vcs_client_vsn(svn) -> {1, 6};
+required_vcs_client_vsn(rsync) -> {2, 0};
+required_vcs_client_vsn(fossil) -> {1, 0}.
vcs_client_vsn(hg) ->
vcs_client_vsn(rebar_utils:find_executable("hg"), " --version",
@@ -486,7 +563,10 @@ vcs_client_vsn(svn) ->
"svn, version (\\d+).(\\d+)");
vcs_client_vsn(rsync) ->
vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version",
- "rsync version (\\d+).(\\d+)").
+ "rsync version (\\d+).(\\d+)");
+vcs_client_vsn(fossil) ->
+ vcs_client_vsn(rebar_utils:find_executable("fossil"), " version",
+ "version (\\d+).(\\d+)").
has_vcs_dir(git, Dir) ->
filelib:is_dir(filename:join(Dir, ".git"));
diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl
index 5d85146..cf0239c 100644
--- a/src/rebar_edoc.erl
+++ b/src/rebar_edoc.erl
@@ -45,28 +45,16 @@
%% Public API
%% ===================================================================
-%% @doc Generate Erlang program documentation.
--spec doc(Config::rebar_config:config(), File::file:filename()) -> ok.
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, AppName, _AppData} = rebar_app_utils:load_app_file(File),
+ {ok, Config1, AppName, _AppData} =
+ rebar_app_utils:load_app_file(Config, File),
- %% Determine the age of the summary file
- EDocInfoName = filename:join(proplists:get_value(dir, EDocOpts, "doc"),
- "edoc-info"),
- EDocInfoLastMod = filelib:last_modified(EDocInfoName),
-
- %% For each source directory, look for a more recent file than
- %% SumaryLastMod; in that case, we go ahead and do a full regen
- NeedsRegen = newer_file_exists(proplists:get_value(source_path,
- EDocOpts, ["src"]),
- EDocInfoLastMod),
-
- case NeedsRegen of
+ case needs_regen(EDocOpts) of
true ->
?INFO("Regenerating edocs for ~p\n", [AppName]),
ok = edoc:application(AppName, ".", EDocOpts);
@@ -77,7 +65,7 @@ doc(Config, File) ->
%% Restore code path
true = code:set_path(CodePath),
- ok.
+ {ok, Config1}.
%% ===================================================================
%% Internal functions
@@ -88,33 +76,44 @@ setup_code_path() ->
%% and the like can work properly when generating their own
%% documentation.
CodePath = code:get_path(),
- true = code:add_patha(ebin_dir()),
+ true = code:add_patha(rebar_utils:ebin_dir()),
CodePath.
-ebin_dir() ->
- filename:join(rebar_utils:get_cwd(), "ebin").
+-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,
-newer_file_exists(Paths, LastMod) ->
- CheckFile = fun(Filename, _) ->
- FLast = filelib:last_modified(Filename),
- case FLast > LastMod of
- true ->
- ?DEBUG("~p is more recent than edoc-info: "
- "~120p > ~120p\n",
- [Filename, FLast, LastMod]),
- throw(newer_file_exists);
- false ->
- false
- end
- end,
try
- lists:foldl(fun(P, _) ->
+ lists:foldl(fun({file, F}, _) ->
+ ThrowIfNewer(F, false);
+ (P, _) ->
filelib:fold_files(P, ".*.erl", true,
- CheckFile, false)
- end, undefined, Paths),
- false
+ ThrowIfNewer, false)
+ end, undefined, Paths)
catch
- throw:newer_file_exists ->
+ 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_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 7f5268d..d704d2d 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -29,8 +29,8 @@
-export([compile/2,
clean/2]).
--export([doterl_compile/2,
- doterl_compile/3]).
+%% for internal use by only eunit and qc
+-export([test_compile/3]).
-include("rebar.hrl").
@@ -68,7 +68,7 @@
%% 'old_inets'}]}.
%%
--spec compile(Config::rebar_config:config(), AppFile::file:filename()) -> 'ok'.
+-spec compile(rebar_config:config(), file:filename()) -> 'ok'.
compile(Config, _AppFile) ->
rebar_base_compiler:run(Config,
check_files(rebar_config:get_local(
@@ -87,7 +87,7 @@ compile(Config, _AppFile) ->
fun compile_mib/3),
doterl_compile(Config, "ebin").
--spec clean(Config::rebar_config:config(), AppFile::file:filename()) -> 'ok'.
+-spec clean(rebar_config:config(), file:filename()) -> 'ok'.
clean(_Config, _AppFile) ->
MibFiles = rebar_utils:find_files("mibs", "^.*\\.mib\$"),
MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
@@ -110,24 +110,127 @@ clean(_Config, _AppFile) ->
lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")),
ok.
+%% ===================================================================
+%% .erl Compilation API (externally used by only eunit and qc)
+%% ===================================================================
+
+test_compile(Config, Cmd, OutDir) ->
+ %% Obtain all the test modules for inclusion in the compile stage.
+ %% Notice: this could also be achieved with the following
+ %% rebar.config option: {test_compile_opts, [{src_dirs, ["test"]}]}
+ TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
+
+ %% Copy source files to eunit dir for cover in case they are not directly
+ %% in src but in a subdirectory of src. Cover only looks in cwd and ../src
+ %% for source files. Also copy files from src_dirs.
+ ErlOpts = rebar_utils:erl_opts(Config),
+
+ SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
+ SrcErls = lists:foldl(
+ fun(Dir, Acc) ->
+ Files = rebar_utils:find_files(Dir, ".*\\.erl\$"),
+ lists:append(Acc, Files)
+ end, [], SrcDirs),
+
+ %% If it is not the first time rebar eunit is executed, there will be source
+ %% files already present in OutDir. Since some SCMs (like Perforce) set
+ %% the source files as being read only (unless they are checked out), we
+ %% need to be sure that the files already present in OutDir are writable
+ %% before doing the copy. This is done here by removing any file that was
+ %% already present before calling rebar_file_utils:cp_r.
+
+ %% Get the full path to a file that was previously copied in OutDir
+ ToCleanUp = fun(F, Acc) ->
+ F2 = filename:basename(F),
+ F3 = filename:join([OutDir, F2]),
+ case filelib:is_regular(F3) of
+ true -> [F3|Acc];
+ false -> Acc
+ end
+ end,
+
+ ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)),
+ ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)),
+
+ ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, OutDir),
+
+ %% Compile erlang code to OutDir, using a tweaked config
+ %% with appropriate defines for eunit, and include all the test modules
+ %% as well.
+ ok = doterl_compile(test_compile_config(Config, Cmd), OutDir, TestErls),
+
+ {ok, SrcErls}.
%% ===================================================================
-%% .erl Compilation API (externally used by only eunit)
+%% Internal functions
%% ===================================================================
--spec doterl_compile(Config::rebar_config:config(),
- OutDir::file:filename()) -> 'ok'.
+test_compile_config(Config, Cmd) ->
+ {Config1, TriqOpts} = triq_opts(Config),
+ {Config2, PropErOpts} = proper_opts(Config1),
+ {Config3, EqcOpts} = eqc_opts(Config2),
+
+ ErlOpts = rebar_config:get_list(Config3, erl_opts, []),
+ OptsAtom = list_to_atom(Cmd ++ "_compile_opts"),
+ EunitOpts = rebar_config:get_list(Config3, OptsAtom, []),
+ Opts0 = [{d, 'TEST'}] ++
+ ErlOpts ++ EunitOpts ++ TriqOpts ++ PropErOpts ++ EqcOpts,
+ Opts = [O || O <- Opts0, O =/= no_debug_info],
+ Config4 = rebar_config:set(Config3, erl_opts, Opts),
+
+ FirstFilesAtom = list_to_atom(Cmd ++ "_first_files"),
+ FirstErls = rebar_config:get_list(Config4, FirstFilesAtom, []),
+ rebar_config:set(Config4, erl_first_files, FirstErls).
+
+triq_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_triq_avail, triq,
+ "triq.hrl", "Triq"),
+ Opts = define_if('TRIQ', IsAvail),
+ {NewConfig, Opts}.
+
+proper_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_proper_avail, proper,
+ "proper.hrl", "PropEr"),
+ Opts = define_if('PROPER', IsAvail),
+ {NewConfig, Opts}.
+
+eqc_opts(Config) ->
+ {NewConfig, IsAvail} = is_lib_avail(Config, is_eqc_avail, eqc,
+ "eqc.hrl", "QuickCheck"),
+ Opts = define_if('EQC', IsAvail),
+ {NewConfig, Opts}.
+
+define_if(Def, true) -> [{d, Def}];
+define_if(_Def, false) -> [].
+
+is_lib_avail(Config, DictKey, Mod, Hrl, Name) ->
+ case rebar_config:get_xconf(Config, DictKey, undefined) of
+ undefined ->
+ IsAvail = case code:lib_dir(Mod, include) of
+ {error, bad_name} ->
+ false;
+ Dir ->
+ filelib:is_regular(filename:join(Dir, Hrl))
+ end,
+ NewConfig = rebar_config:set_xconf(Config, DictKey, IsAvail),
+ ?DEBUG("~s availability: ~p\n", [Name, IsAvail]),
+ {NewConfig, IsAvail};
+ IsAvail ->
+ {Config, IsAvail}
+ end.
+
+-spec doterl_compile(rebar_config:config(), file:filename()) -> 'ok'.
doterl_compile(Config, OutDir) ->
doterl_compile(Config, OutDir, []).
doterl_compile(Config, OutDir, MoreSources) ->
FirstErls = rebar_config:get_list(Config, erl_first_files, []),
- ErlOpts = erl_opts(Config),
+ ErlOpts = rebar_utils:erl_opts(Config),
?DEBUG("erl_opts ~p~n", [ErlOpts]),
%% Support the src_dirs option allowing multiple directories to
%% contain erlang source. This might be used, for example, should
%% eunit tests be separated from the core application source.
- SrcDirs = src_dirs(proplists:append_values(src_dirs, ErlOpts)),
+ SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)),
RestErls = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources,
not lists:member(Source, FirstErls)],
@@ -154,9 +257,10 @@ doterl_compile(Config, OutDir, MoreSources) ->
ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")),
CurrPath = code:get_path(),
true = code:add_path(filename:absname("ebin")),
+ OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir),
rebar_base_compiler:run(Config, NewFirstErls, OtherErls,
fun(S, C) ->
- internal_erl_compile(S, C, OutDir, ErlOpts)
+ internal_erl_compile(C, S, OutDir1, ErlOpts)
end),
true = code:set_path(CurrPath),
ok.
@@ -166,27 +270,15 @@ doterl_compile(Config, OutDir, MoreSources) ->
%% Internal functions
%% ===================================================================
-erl_opts(Config) ->
- RawErlOpts = filter_defines(rebar_config:get(Config, erl_opts, []), []),
- GlobalDefines = [{d, list_to_atom(D)} ||
- D <- rebar_config:get_global(defines, [])],
- Opts = GlobalDefines ++ RawErlOpts,
- case proplists:is_defined(no_debug_info, Opts) of
- true ->
- [O || O <- Opts, O =/= no_debug_info];
- false ->
- [debug_info|Opts]
- end.
-
--spec include_path(Source::file:filename(),
- Config::rebar_config:config()) -> [file:filename(), ...].
+-spec include_path(file:filename(),
+ rebar_config:config()) -> [file:filename(), ...].
include_path(Source, Config) ->
ErlOpts = rebar_config:get(Config, erl_opts, []),
["include", filename:dirname(Source)]
++ proplists:get_all_values(i, ErlOpts).
--spec inspect(Source::file:filename(),
- IncludePath::[file:filename(), ...]) -> {string(), [string()]}.
+-spec inspect(file:filename(),
+ [file:filename(), ...]) -> {string(), [string()]}.
inspect(Source, IncludePath) ->
ModuleDefault = filename:basename(Source, ".erl"),
case epp:open(Source, IncludePath) of
@@ -197,8 +289,8 @@ inspect(Source, IncludePath) ->
{ModuleDefault, []}
end.
--spec inspect_epp(Epp::pid(), Source::file:filename(), Module::file:filename(),
- Includes::[string()]) -> {string(), [string()]}.
+-spec inspect_epp(pid(), file:filename(), file:filename(),
+ [string()]) -> {string(), [string()]}.
inspect_epp(Epp, Source, Module, Includes) ->
case epp:parse_erl_form(Epp) of
{ok, {attribute, _, module, ModInfo}} ->
@@ -233,18 +325,16 @@ inspect_epp(Epp, Source, Module, Includes) ->
inspect_epp(Epp, Source, Module, Includes)
end.
--spec needs_compile(Source::file:filename(), Target::file:filename(),
- Hrls::[string()]) -> boolean().
+-spec needs_compile(file:filename(), file:filename(),
+ [string()]) -> boolean().
needs_compile(Source, Target, Hrls) ->
TargetLastMod = filelib:last_modified(Target),
lists:any(fun(I) -> TargetLastMod < filelib:last_modified(I) end,
[Source] ++ Hrls).
--spec internal_erl_compile(Source::file:filename(),
- Config::rebar_config:config(),
- Outdir::file:filename(),
- ErlOpts::list()) -> 'ok' | 'skipped'.
-internal_erl_compile(Source, Config, Outdir, ErlOpts) ->
+-spec internal_erl_compile(rebar_config:config(), file:filename(),
+ file:filename(), list()) -> 'ok' | 'skipped'.
+internal_erl_compile(Config, Source, Outdir, ErlOpts) ->
%% Determine the target name and includes list by inspecting the source file
{Module, Hrls} = inspect(Source, include_path(Source, Config)),
@@ -262,16 +352,17 @@ internal_erl_compile(Source, Config, Outdir, ErlOpts) ->
{ok, _Mod} ->
ok;
{ok, _Mod, Ws} ->
- rebar_base_compiler:ok_tuple(Source, Ws);
+ rebar_base_compiler:ok_tuple(Config, Source, Ws);
{error, Es, Ws} ->
- rebar_base_compiler:error_tuple(Source, Es, Ws, Opts)
+ rebar_base_compiler:error_tuple(Config, Source,
+ Es, Ws, Opts)
end;
false ->
skipped
end.
--spec compile_mib(Source::file:filename(), Target::file:filename(),
- Config::rebar_config:config()) -> 'ok'.
+-spec compile_mib(file:filename(), file:filename(),
+ rebar_config:config()) -> 'ok'.
compile_mib(Source, Target, Config) ->
ok = rebar_utils:ensure_dir(Target),
ok = rebar_utils:ensure_dir(filename:join("include", "dummy.hrl")),
@@ -285,33 +376,34 @@ compile_mib(Source, Target, Config) ->
rebar_file_utils:mv(Hrl_filename, "include"),
ok;
{error, compilation_failed} ->
- ?ABORT
+ ?FAIL
end.
--spec compile_xrl(Source::file:filename(), Target::file:filename(),
- Config::rebar_config:config()) -> 'ok'.
+-spec compile_xrl(file:filename(), file:filename(),
+ rebar_config:config()) -> 'ok'.
compile_xrl(Source, Target, Config) ->
Opts = [{scannerfile, Target} | rebar_config:get(Config, xrl_opts, [])],
- compile_xrl_yrl(Source, Target, Opts, leex).
+ compile_xrl_yrl(Config, Source, Target, Opts, leex).
--spec compile_yrl(Source::file:filename(), Target::file:filename(),
- Config::rebar_config:config()) -> 'ok'.
+-spec compile_yrl(file:filename(), file:filename(),
+ rebar_config:config()) -> 'ok'.
compile_yrl(Source, Target, Config) ->
Opts = [{parserfile, Target} | rebar_config:get(Config, yrl_opts, [])],
- compile_xrl_yrl(Source, Target, Opts, yecc).
+ compile_xrl_yrl(Config, Source, Target, Opts, yecc).
--spec compile_xrl_yrl(Source::file:filename(), Target::file:filename(),
- Opts::list(), Mod::atom()) -> 'ok'.
-compile_xrl_yrl(Source, Target, Opts, Mod) ->
+-spec compile_xrl_yrl(rebar_config:config(), file:filename(),
+ file:filename(), list(), module()) -> 'ok'.
+compile_xrl_yrl(Config, Source, Target, Opts, Mod) ->
case needs_compile(Source, Target, []) of
true ->
case Mod:file(Source, Opts ++ [{return, true}]) of
{ok, _} ->
ok;
{ok, _Mod, Ws} ->
- rebar_base_compiler:ok_tuple(Source, Ws);
+ rebar_base_compiler:ok_tuple(Config, Source, Ws);
{error, Es, Ws} ->
- rebar_base_compiler:error_tuple(Source, Es, Ws, Opts)
+ rebar_base_compiler:error_tuple(Config, Source,
+ Es, Ws, Opts)
end;
false ->
skipped
@@ -322,27 +414,21 @@ gather_src([], Srcs) ->
gather_src([Dir|Rest], Srcs) ->
gather_src(Rest, Srcs ++ rebar_utils:find_files(Dir, ".*\\.erl\$")).
--spec src_dirs(SrcDirs::[string()]) -> [file:filename(), ...].
-src_dirs([]) ->
- ["src"];
-src_dirs(SrcDirs) ->
- SrcDirs.
--spec dirs(Dir::file:filename()) -> [file:filename()].
+-spec dirs(file:filename()) -> [file:filename()].
dirs(Dir) ->
[F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)].
--spec delete_dir(Dir::file:filename(),
- Subdirs::[string()]) -> 'ok' | {'error', atom()}.
+-spec delete_dir(file:filename(), [string()]) -> 'ok' | {'error', atom()}.
delete_dir(Dir, []) ->
file:del_dir(Dir);
delete_dir(Dir, Subdirs) ->
lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
file:del_dir(Dir).
--spec compile_priority(File::file:filename()) -> 'normal' | 'behaviour' |
- 'callback' |
- 'parse_transform'.
+-spec compile_priority(file:filename()) -> 'normal' | 'behaviour' |
+ 'callback' |
+ 'parse_transform'.
compile_priority(File) ->
case epp_dodger:parse_file(File) of
{error, _} ->
@@ -375,33 +461,9 @@ compile_priority(File) ->
end.
%%
-%% Filter a list of erl_opts platform_define options such that only
-%% those which match the provided architecture regex are returned.
-%%
--spec filter_defines(ErlOpts::list(), Acc::list()) -> list().
-filter_defines([], Acc) ->
- lists:reverse(Acc);
-filter_defines([{platform_define, ArchRegex, Key} | Rest], Acc) ->
- case rebar_utils:is_arch(ArchRegex) of
- true ->
- filter_defines(Rest, [{d, Key} | Acc]);
- false ->
- filter_defines(Rest, Acc)
- end;
-filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) ->
- case rebar_utils:is_arch(ArchRegex) of
- true ->
- filter_defines(Rest, [{d, Key, Value} | Acc]);
- false ->
- filter_defines(Rest, Acc)
- end;
-filter_defines([Opt | Rest], Acc) ->
- filter_defines(Rest, [Opt | Acc]).
-
-%%
%% Ensure all files in a list are present and abort if one is missing
%%
--spec check_files(FileList::[file:filename()]) -> [file:filename()].
+-spec check_files([file:filename()]) -> [file:filename()].
check_files(FileList) ->
[check_file(F) || F <- FileList].
diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl
index 2a9cb63..12077f2 100644
--- a/src/rebar_erlydtl_compiler.erl
+++ b/src/rebar_erlydtl_compiler.erl
@@ -83,7 +83,7 @@
compile(Config, _AppFile) ->
DtlOpts = erlydtl_opts(Config),
OrigPath = code:get_path(),
- true = code:add_path(filename:join(rebar_utils:get_cwd(), "ebin")),
+ true = code:add_path(rebar_utils:ebin_dir()),
Result = rebar_base_compiler:run(Config, [],
option(doc_root, DtlOpts),
option(source_ext, DtlOpts),
@@ -120,7 +120,7 @@ compile_dtl(Source, Target, Config) ->
" http://code.google.com/p/erlydtl/~n"
" and install it into your erlang library dir~n"
"===============================================~n~n", []),
- ?ABORT;
+ ?FAIL;
_ ->
case needs_compile(Source, Target, Config) of
true ->
@@ -146,7 +146,7 @@ do_compile(Source, Target, Config) ->
Reason ->
?ERROR("Compiling template ~s failed:~n ~p~n",
[Source, Reason]),
- ?ABORT
+ ?FAIL
end.
module_name(Target) ->
diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl
index 1258898..706cf7c 100644
--- a/src/rebar_escripter.erl
+++ b/src/rebar_escripter.erl
@@ -36,10 +36,11 @@
%% Public API
%% ===================================================================
-escriptize(Config, AppFile) ->
+escriptize(Config0, AppFile) ->
%% Extract the application name from the archive -- this is the default
%% name of the generated script
- AppName = rebar_app_utils:app_name(AppFile),
+ {Config, AppName} = rebar_app_utils:app_name(Config0, AppFile),
+ AppNameStr = atom_to_list(AppName),
%% Get the output filename for the escript -- this may include dirs
Filename = rebar_config:get_local(Config, escript_name, AppName),
@@ -51,13 +52,16 @@ escriptize(Config, AppFile) ->
InclBeams = get_app_beams(
rebar_config:get_local(Config, escript_incl_apps, []), []),
- %% Look for a list extra files to include in the output file.
+ %% Look for a list of extra files to include in the output file.
%% For internal rebar-private use only. Do not use outside rebar.
InclExtra = get_extra(Config),
%% Construct the archive of everything in ebin/ dir -- put it on the
%% top-level of the zip file so that code loading works properly.
- Files = load_files("*", "ebin") ++ InclBeams ++ InclExtra,
+ EbinPrefix = filename:join(AppNameStr, "ebin"),
+ EbinFiles = usort(load_files(EbinPrefix, "*", "ebin")),
+ ExtraFiles = usort(InclBeams ++ InclExtra),
+ Files = EbinFiles ++ ExtraFiles,
case zip:create("mem", Files, [memory]) of
{ok, {"mem", ZipBin}} ->
@@ -66,7 +70,10 @@ escriptize(Config, AppFile) ->
Shebang = rebar_config:get(Config, escript_shebang,
"#!/usr/bin/env escript\n"),
Comment = rebar_config:get(Config, escript_comment, "%%\n"),
- EmuArgs = rebar_config:get(Config, escript_emu_args, "%%!\n"),
+ DefaultEmuArgs = ?FMT("%%! -pa ~s/~s/ebin\n",
+ [AppNameStr, AppNameStr]),
+ EmuArgs = rebar_config:get(Config, escript_emu_args,
+ DefaultEmuArgs),
Script = iolist_to_binary([Shebang, Comment, EmuArgs, ZipBin]),
case file:write_file(Filename, Script) of
ok ->
@@ -74,27 +81,28 @@ escriptize(Config, AppFile) ->
{error, WriteError} ->
?ERROR("Failed to write ~p script: ~p\n",
[AppName, WriteError]),
- ?ABORT
+ ?FAIL
end;
{error, ZipError} ->
?ERROR("Failed to construct ~p escript: ~p\n",
[AppName, ZipError]),
- ?ABORT
+ ?FAIL
end,
%% Finally, update executable perms for our script
{ok, #file_info{mode = Mode}} = file:read_file_info(Filename),
- ok = file:change_mode(Filename, Mode bor 8#00100),
- ok.
+ ok = file:change_mode(Filename, Mode bor 8#00111),
+ {ok, Config}.
-clean(Config, AppFile) ->
+clean(Config0, AppFile) ->
%% Extract the application name from the archive -- this is the default
%% name of the generated script
- AppName = rebar_app_utils:app_name(AppFile),
+ {Config, AppName} = rebar_app_utils:app_name(Config0, AppFile),
%% Get the output filename for the escript -- this may include dirs
Filename = rebar_config:get_local(Config, escript_name, AppName),
- rebar_file_utils:delete_each([Filename]).
+ rebar_file_utils:delete_each([Filename]),
+ {ok, Config}.
%% ===================================================================
%% Internal functions
@@ -108,9 +116,8 @@ get_app_beams([App | Rest], Acc) ->
?ABORT("Failed to get ebin/ directory for "
"~p escript_incl_apps.", [App]);
Path ->
- Acc2 = [{filename:join([App, ebin, F]),
- file_contents(filename:join(Path, F))} ||
- F <- filelib:wildcard("*", Path)],
+ Prefix = filename:join(atom_to_list(App), "ebin"),
+ Acc2 = load_files(Prefix, "*", Path),
get_app_beams(Rest, Acc2 ++ Acc)
end.
@@ -121,11 +128,43 @@ get_extra(Config) ->
end, [], Extra).
load_files(Wildcard, Dir) ->
- [read_file(Filename, Dir) || Filename <- filelib:wildcard(Wildcard, Dir)].
-
-read_file(Filename, Dir) ->
- {Filename, file_contents(filename:join(Dir, Filename))}.
+ load_files("", Wildcard, Dir).
+
+load_files(Prefix, Wildcard, Dir) ->
+ [read_file(Prefix, Filename, Dir)
+ || Filename <- filelib:wildcard(Wildcard, Dir)].
+
+read_file(Prefix, Filename, Dir) ->
+ Filename1 = case Prefix of
+ "" ->
+ Filename;
+ _ ->
+ filename:join([Prefix, Filename])
+ end,
+ [dir_entries(filename:dirname(Filename1)),
+ {Filename1, file_contents(filename:join(Dir, Filename))}].
file_contents(Filename) ->
{ok, Bin} = file:read_file(Filename),
Bin.
+
+%% Given a filename, return zip archive dir entries for each sub-dir.
+%% Required to work around issues fixed in OTP-10071.
+dir_entries(File) ->
+ Dirs = dirs(File),
+ [{Dir ++ "/", <<>>} || Dir <- Dirs].
+
+%% Given "foo/bar/baz", return ["foo", "foo/bar", "foo/bar/baz"].
+dirs(Dir) ->
+ dirs1(filename:split(Dir), "", []).
+
+dirs1([], _, Acc) ->
+ lists:reverse(Acc);
+dirs1([H|T], "", []) ->
+ dirs1(T, H, [H]);
+dirs1([H|T], Last, Acc) ->
+ Dir = filename:join(Last, H),
+ dirs1(T, Dir, [Dir|Acc]).
+
+usort(List) ->
+ lists:ukeysort(1, lists:flatten(List)).
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index 21228df..b82da0f 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -28,7 +28,7 @@
%% @doc rebar_eunit supports the following commands:
%% <ul>
%% <li>eunit - runs eunit tests</li>
-%% <li>clean - remove .eunit directory</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:
@@ -43,12 +43,25 @@
%% The following Global options are supported:
%% <ul>
%% <li>verbose=1 - show extra output from the eunit test</li>
-%% <li>suites="foo,bar" - runs test/foo_tests.erl and test/bar_tests.erl</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, ["dir"]}]}.</code>.
+%% <code>{test_compile_opts, [{src_dirs, ["dir"]}]}.</code>.
%% @copyright 2009, 2010 Dave Smith
%% -------------------------------------------------------------------
-module(rebar_eunit).
@@ -65,64 +78,35 @@
%% ===================================================================
eunit(Config, _AppFile) ->
- %% Make sure ?EUNIT_DIR/ and ebin/ directory exists (tack on dummy module)
- ok = filelib:ensure_dir(eunit_dir() ++ "/foo"),
- ok = filelib:ensure_dir(ebin_dir() ++ "/foo"),
-
- %% 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(ebin_dir()),
-
- %% Obtain all the test modules for inclusion in the compile stage.
- %% Notice: this could also be achieved with the following
- %% rebar.config option: {eunit_compile_opts, [{src_dirs, ["test"]}]}
- TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
-
- %% Copy source files to eunit dir for cover in case they are not directly
- %% in src but in a subdirectory of src. Cover only looks in cwd and ../src
- %% for source files.
- SrcErls = rebar_utils:find_files("src", ".*\\.erl\$"),
-
- %% If it is not the first time rebar eunit is executed, there will be source
- %% files already present in ?EUNIT_DIR. Since some SCMs (like Perforce) set
- %% the source files as being read only (unless they are checked out), we
- %% need to be sure that the files already present in ?EUNIT_DIR are writable
- %% before doing the copy. This is done here by removing any file that was
- %% already present before calling rebar_file_utils:cp_r.
-
- %% Get the full path to a file that was previously copied in ?EUNIT_DIR
- ToCleanUp = fun(F, Acc) ->
- F2 = filename:basename(F),
- F3 = filename:join([?EUNIT_DIR, F2]),
- case filelib:is_regular(F3) of
- true -> [F3|Acc];
- false -> Acc
- end
- end,
-
- ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)),
- ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)),
+ ok = ensure_dirs(),
+ %% Save code path
+ CodePath = setup_code_path(),
+ CompileOnly = rebar_utils:get_experimental_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.
- ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, ?EUNIT_DIR),
+clean(_Config, _File) ->
+ rebar_file_utils:rm_rf(?EUNIT_DIR).
- %% Compile erlang code to ?EUNIT_DIR, using a tweaked config
- %% with appropriate defines for eunit, and include all the test modules
- %% as well.
- rebar_erlc_compiler:doterl_compile(eunit_config(Config),
- ?EUNIT_DIR, TestErls),
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+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. Filter out "*_tests" modules so
- %% eunit won't doubly run them and so cover only calculates
- %% coverage on production code. However, keep "*_tests" modules
- %% that are not automatically included by eunit.
+ %% compilation info to be lost.
+
AllBeamFiles = rebar_utils:beams(?EUNIT_DIR),
{BeamFiles, TestBeamFiles} =
lists:partition(fun(N) -> string:str(N, "_tests.beam") =:= 0 end,
@@ -130,17 +114,22 @@ eunit(Config, _AppFile) ->
OtherBeamFiles = TestBeamFiles --
[filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles],
ModuleBeamFiles = BeamFiles ++ OtherBeamFiles,
- Modules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- ModuleBeamFiles],
+
+ %% Get modules to be run in eunit
+ AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles],
+ {SuitesProvided, FilteredModules} = filter_suites(Config, AllModules),
+
+ %% Get matching tests
+ Tests = get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules),
+
SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls],
- Suites = get_suites(),
- FilteredModules = filtered_modules(Modules, Suites),
{ok, CoverLog} = cover_init(Config, ModuleBeamFiles),
StatusBefore = status_before_eunit(),
- EunitResult = perform_eunit(Config, FilteredModules),
- perform_cover(Config, FilteredModules, SrcModules),
+ EunitResult = perform_eunit(Config, Tests),
+ perform_cover(Config, FilteredModules, SrcModules),
cover_close(CoverLog),
case proplists:get_value(reset_after_eunit, get_eunit_opts(Config),
@@ -151,6 +140,10 @@ eunit(Config, _AppFile) ->
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 = cover:stop(),
+
case EunitResult of
ok ->
ok;
@@ -162,29 +155,206 @@ eunit(Config, _AppFile) ->
true = code:set_path(CodePath),
ok.
-clean(_Config, _File) ->
- rebar_file_utils:rm_rf(?EUNIT_DIR).
-
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
+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).
-ebin_dir() ->
- filename:join(rebar_utils:get_cwd(), "ebin").
+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.
+
+%%
+%% == filter suites ==
+%%
-get_suites() ->
- Suites = rebar_utils:get_deprecated_global(suite, suites, [], "soon"),
- [list_to_atom(Suite) || Suite <- string:tokens(Suites, ",")].
+filter_suites(Config, Modules) ->
+ RawSuites = rebar_config:get_global(Config, suites, ""),
+ SuitesProvided = RawSuites =/= "",
+ Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")],
+ {SuitesProvided, filter_suites1(Modules, Suites)}.
-filtered_modules(Modules, []) ->
+filter_suites1(Modules, []) ->
Modules;
-filtered_modules(Modules, Suites) ->
+filter_suites1(Modules, Suites) ->
[M || M <- Modules, lists:member(M, Suites)].
-perform_eunit(Config, FilteredModules) ->
+%%
+%% == get matching tests ==
+%%
+get_tests(Config, SuitesProvided, ModuleBeamFiles, FilteredModules) ->
+ Modules = case SuitesProvided of
+ false ->
+ %% No specific suites 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)."
+ [rebar_utils:beam_to_mod(?EUNIT_DIR, N) ||
+ N <- ModuleBeamFiles];
+ true ->
+ %% Specific suites have been provided, return the
+ %% filtered modules
+ FilteredModules
+ end,
+ get_matching_tests(Config, Modules).
+
+get_matching_tests(Config, Modules) ->
+ RawFunctions = rebar_utils:get_experimental_global(Config, tests, ""),
+ Tests = [list_to_atom(F1) || F1 <- string:tokens(RawFunctions, ",")],
+ case Tests of
+ [] ->
+ Modules;
+ Functions ->
+ case get_matching_tests1(Modules, Functions, []) of
+ [] ->
+ [];
+ RawTests ->
+ make_test_primitives(RawTests)
+ end
+ end.
+
+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
@@ -192,7 +362,7 @@ perform_eunit(Config, FilteredModules) ->
Cwd = rebar_utils:get_cwd(),
ok = file:set_cwd(?EUNIT_DIR),
- EunitResult = (catch eunit:test(FilteredModules, EunitOpts)),
+ EunitResult = (catch eunit:test(Tests, EunitOpts)),
%% Return to original working dir
ok = file:set_cwd(Cwd),
@@ -201,7 +371,7 @@ perform_eunit(Config, FilteredModules) ->
get_eunit_opts(Config) ->
%% Enable verbose in eunit if so requested..
- BaseOpts = case rebar_config:is_verbose() of
+ BaseOpts = case rebar_config:is_verbose(Config) of
true ->
[verbose];
false ->
@@ -210,46 +380,9 @@ get_eunit_opts(Config) ->
BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []).
-eunit_config(Config) ->
- EqcOpts = eqc_opts(),
- PropErOpts = proper_opts(),
-
- ErlOpts = rebar_config:get_list(Config, erl_opts, []),
- EunitOpts = rebar_config:get_list(Config, eunit_compile_opts, []),
- Opts0 = [{d, 'TEST'}] ++
- ErlOpts ++ EunitOpts ++ EqcOpts ++ PropErOpts,
- Opts = [O || O <- Opts0, O =/= no_debug_info],
- Config1 = rebar_config:set(Config, erl_opts, Opts),
-
- FirstErls = rebar_config:get_list(Config1, eunit_first_files, []),
- rebar_config:set(Config1, erl_first_files, FirstErls).
-
-eqc_opts() ->
- define_if('EQC', is_lib_avail(is_eqc_avail, eqc,
- "eqc.hrl", "QuickCheck")).
-
-proper_opts() ->
- define_if('PROPER', is_lib_avail(is_proper_avail, proper,
- "proper.hrl", "PropEr")).
-
-define_if(Def, true) -> [{d, Def}];
-define_if(_Def, false) -> [].
-
-is_lib_avail(DictKey, Mod, Hrl, Name) ->
- case erlang:get(DictKey) of
- undefined ->
- IsAvail = case code:lib_dir(Mod, include) of
- {error, bad_name} ->
- false;
- Dir ->
- filelib:is_regular(filename:join(Dir, Hrl))
- end,
- erlang:put(DictKey, IsAvail),
- ?DEBUG("~s availability: ~p\n", [Name, IsAvail]),
- IsAvail;
- IsAvail ->
- IsAvail
- end.
+%%
+%% == code coverage ==
+%%
perform_cover(Config, BeamFiles, SrcModules) ->
perform_cover(rebar_config:get(Config, cover_enabled, false),
@@ -264,7 +397,9 @@ cover_analyze(_Config, [], _SrcModules) ->
ok;
cover_analyze(Config, FilteredModules, SrcModules) ->
%% Generate coverage info for all the cover-compiled modules
- Coverage = lists:flatten([cover_analyze_mod(M) || M <- FilteredModules]),
+ Coverage = lists:flatten([cover_analyze_mod(M)
+ || M <- FilteredModules,
+ cover:is_compiled(M) =/= false]),
%% Write index of coverage info
cover_write_index(lists:sort(Coverage), SrcModules),
@@ -278,6 +413,14 @@ cover_analyze(Config, FilteredModules, SrcModules) ->
Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]),
?CONSOLE("Cover analysis: ~s\n", [Index]),
+ %% Export coverage data, if configured
+ case rebar_config:get(Config, cover_export_enabled, false) of
+ true ->
+ cover_export_coverdata();
+ false ->
+ ok
+ end,
+
%% Print coverage report, if configured
case rebar_config:get(Config, cover_print_enabled, false) of
true ->
@@ -294,17 +437,17 @@ cover_close(F) ->
cover_init(false, _BeamFiles) ->
{ok, not_enabled};
cover_init(true, BeamFiles) ->
- %% Attempt to start the cover server, then set it's group leader to
+ %% Attempt to start the cover server, then set its group leader to
%% .eunit/cover.log, so all cover log messages will go there instead of
- %% to stdout. If the cover server is already started we'll reuse that
- %% pid.
- {ok, CoverPid} = case cover:start() of
- {ok, _P} = OkStart ->
- OkStart;
- {error,{already_started, P}} ->
- {ok, P};
- {error, _Reason} = ErrorStart ->
- ErrorStart
+ %% 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(
@@ -313,9 +456,6 @@ cover_init(true, BeamFiles) ->
group_leader(F, CoverPid),
- %% Make sure any previous runs of cover don't unduly influence
- cover:reset(),
-
?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]),
Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles],
@@ -323,7 +463,7 @@ cover_init(true, BeamFiles) ->
[] ->
%% No modules compiled successfully...fail
?ERROR("Cover failed to compile any modules; aborting.~n", []),
- ?ABORT;
+ ?FAIL;
_ ->
%% At least one module compiled successfully
@@ -395,7 +535,7 @@ cover_write_index(Coverage, SrcModules) ->
cover_write_index_section(_F, _SectionName, []) ->
ok;
cover_write_index_section(F, SectionName, Coverage) ->
- %% Calculate total coverage %
+ %% Calculate total coverage
{Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) ->
{CAcc + C, NAcc + N}
end, {0, 0}, Coverage),
@@ -443,19 +583,32 @@ cover_print_coverage(Coverage) ->
cover_file(Module) ->
filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]).
+cover_export_coverdata() ->
+ ExportFile = filename:join(eunit_dir(), "eunit.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)) ++ "%".
-get_app_names() ->
- [AppName || {AppName, _, _} <- application:loaded_applications()].
+%%
+%% == 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 ->
diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl
index 6eb2ab1..fcd9c5e 100644
--- a/src/rebar_file_utils.erl
+++ b/src/rebar_file_utils.erl
@@ -29,7 +29,8 @@
-export([rm_rf/1,
cp_r/2,
mv/2,
- delete_each/1]).
+ delete_each/1,
+ write_file_if_contents_differ/2]).
-include("rebar.hrl").
@@ -39,7 +40,7 @@
%% @doc Remove files and directories.
%% Target is a single filename, directoryname or wildcard expression.
--spec rm_rf(Target::string()) -> ok.
+-spec rm_rf(string()) -> 'ok'.
rm_rf(Target) ->
case os:type() of
{unix, _} ->
@@ -56,7 +57,9 @@ rm_rf(Target) ->
ok
end.
--spec cp_r(Sources::list(string()), Dest::file:filename()) -> ok.
+-spec cp_r(list(string()), file:filename()) -> 'ok'.
+cp_r([], _Dest) ->
+ ok;
cp_r(Sources, Dest) ->
case os:type() of
{unix, _} ->
@@ -71,7 +74,7 @@ cp_r(Sources, Dest) ->
ok
end.
--spec mv(Source::string(), Dest::file:filename()) -> ok.
+-spec mv(string(), file:filename()) -> 'ok'.
mv(Source, Dest) ->
case os:type() of
{unix, _} ->
@@ -106,7 +109,18 @@ delete_each([File | Rest]) ->
delete_each(Rest);
{error, Reason} ->
?ERROR("Failed to delete file ~s: ~p\n", [File, Reason]),
- ?ABORT
+ ?FAIL
+ end.
+
+write_file_if_contents_differ(Filename, Bytes) ->
+ ToWrite = iolist_to_binary(Bytes),
+ case file:read_file(Filename) of
+ {ok, ToWrite} ->
+ ok;
+ {ok, _} ->
+ file:write_file(Filename, ToWrite);
+ {error, _} ->
+ file:write_file(Filename, ToWrite)
end.
%% ===================================================================
diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl
index d688e9c..d288ca5 100644
--- a/src/rebar_lfe_compiler.erl
+++ b/src/rebar_lfe_compiler.erl
@@ -57,16 +57,17 @@ compile_lfe(Source, _Target, Config) ->
" {git, \"git://github.com/rvirding/lfe\",~n"
" {tag, \"v0.6.1\"}}}~n"
"~n", []),
- ?ABORT;
+ ?FAIL;
_ ->
Opts = [{i, "include"}, {outdir, "ebin"}, return]
++ rebar_config:get_list(Config, erl_opts, []),
case lfe_comp:file(Source, Opts) of
{ok, _Mod, Ws} ->
- rebar_base_compiler:ok_tuple(Source, Ws);
+ rebar_base_compiler:ok_tuple(Config, Source, Ws);
{error, Es, Ws} ->
- rebar_base_compiler:error_tuple(Source, Es, Ws, Opts);
+ rebar_base_compiler:error_tuple(Config, Source,
+ Es, Ws, Opts);
_ ->
- ?ABORT
+ ?FAIL
end
end.
diff --git a/src/rebar_log.erl b/src/rebar_log.erl
index b7529a9..4108c9c 100644
--- a/src/rebar_log.erl
+++ b/src/rebar_log.erl
@@ -26,16 +26,17 @@
%% -------------------------------------------------------------------
-module(rebar_log).
--export([init/0,
- set_level/1, get_level/0, default_level/0,
+-export([init/1,
+ set_level/1, default_level/0,
log/3]).
%% ===================================================================
%% Public API
%% ===================================================================
-init() ->
- case valid_level(rebar_config:get_global(verbose, error_level())) of
+init(Config) ->
+ Verbosity = rebar_config:get_global(Config, verbose, default_level()),
+ case valid_level(Verbosity) of
0 -> set_level(error);
1 -> set_level(warn);
2 -> set_level(info);
@@ -45,14 +46,6 @@ init() ->
set_level(Level) ->
ok = application:set_env(rebar, log_level, Level).
-get_level() ->
- case application:get_env(rebar, log_level) of
- undefined ->
- error;
- {ok, Value} ->
- Value
- end.
-
log(Level, Str, Args) ->
{ok, LogLevel} = application:get_env(rebar, log_level),
case should_log(LogLevel, Level) of
diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl
index d0f618f..9bff892 100644
--- a/src/rebar_neotoma_compiler.erl
+++ b/src/rebar_neotoma_compiler.erl
@@ -80,7 +80,7 @@ compile_neo(Source, Target, Config) ->
" https://github.com/seancribbs/neotoma~n"
" and install it into your erlang library dir~n"
"===============================================~n~n", []),
- ?ABORT;
+ ?FAIL;
_ ->
case needs_compile(Source, Target, Config) of
true ->
@@ -104,7 +104,7 @@ do_compile(Source, _Target, Config) ->
Reason ->
?ERROR("Compiling peg ~s failed:~n ~p~n",
[Source, Reason]),
- ?ABORT
+ ?FAIL
end.
needs_compile(Source, Target, Config) ->
diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl
index 0b2e8be..a62f584 100644
--- a/src/rebar_otp_app.erl
+++ b/src/rebar_otp_app.erl
@@ -39,27 +39,27 @@ compile(Config, File) ->
%% 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.
- AppFile = case rebar_app_utils:is_app_src(File) of
- true ->
- preprocess(Config, File);
- false ->
- File
- end,
+ {Config1, AppFile} = case rebar_app_utils:is_app_src(File) of
+ true ->
+ preprocess(Config, File);
+ false ->
+ {Config, File}
+ end,
%% Load the app file and validate it.
- case rebar_app_utils:load_app_file(AppFile) of
- {ok, AppName, AppData} ->
+ case rebar_app_utils:load_app_file(Config1, AppFile) of
+ {ok, Config2, AppName, AppData} ->
validate_name(AppName, AppFile),
%% In general, the list of modules is an important thing to validate
%% for compliance with OTP guidelines and upgrade procedures.
%% However, some people prefer not to validate this list.
- case rebar_config:get_local(Config, validate_app_modules, true) of
+ case rebar_config:get_local(Config1, validate_app_modules, true) of
true ->
- validate_modules(AppName,
- proplists:get_value(modules, AppData));
+ Modules = proplists:get_value(modules, AppData),
+ {validate_modules(AppName, Modules), Config2};
false ->
- ok
+ {ok, Config2}
end;
{error, Reason} ->
?ABORT("Failed to load app file ~s: ~p\n", [AppFile, Reason])
@@ -88,17 +88,17 @@ clean(_Config, File) ->
%% ===================================================================
preprocess(Config, AppSrcFile) ->
- case rebar_app_utils:load_app_file(AppSrcFile) of
- {ok, AppName, AppData} ->
+ case rebar_app_utils:load_app_file(Config, AppSrcFile) of
+ {ok, Config1, AppName, AppData} ->
%% Look for a configuration file with vars we want to
%% substitute. Note that we include the list of modules available in
%% ebin/ and update the app data accordingly.
- AppVars = load_app_vars(Config) ++ [{modules, ebin_modules()}],
+ AppVars = load_app_vars(Config1) ++ [{modules, ebin_modules()}],
A1 = apply_app_vars(AppVars, AppData),
%% AppSrcFile may contain instructions for generating a vsn number
- Vsn = rebar_app_utils:app_vsn(AppSrcFile),
+ {Config2, Vsn} = rebar_app_utils:app_vsn(Config1, AppSrcFile),
A2 = lists:keystore(vsn, 1, A1, {vsn, Vsn}),
%% Build the final spec as a string
@@ -106,13 +106,13 @@ preprocess(Config, AppSrcFile) ->
%% Setup file .app filename and write new contents
AppFile = rebar_app_utils:app_src_to_app(AppSrcFile),
- ok = file:write_file(AppFile, Spec),
+ ok = rebar_file_utils:write_file_if_contents_differ(AppFile, Spec),
%% Make certain that the ebin/ directory is available
%% on the code path
true = code:add_path(filename:absname(filename:dirname(AppFile))),
- AppFile;
+ {Config2, AppFile};
{error, Reason} ->
?ABORT("Failed to read ~s for preprocessing: ~p\n",
@@ -146,12 +146,12 @@ validate_name(AppName, File) ->
false ->
?ERROR("Invalid ~s: name of application (~p) "
"must match filename.\n", [File, AppName]),
- ?ABORT
+ ?FAIL
end.
validate_modules(AppName, undefined) ->
?ERROR("Missing modules declaration in ~p.app~n", [AppName]),
- ?ABORT;
+ ?FAIL;
validate_modules(AppName, Mods) ->
%% Construct two sets -- one for the actual .beam files in ebin/
@@ -169,7 +169,7 @@ validate_modules(AppName, Mods) ->
M <- MissingBeams]),
?ERROR("One or more modules listed in ~p.app are not "
"present in ebin/*.beam:\n~s", [AppName, Msg1]),
- ?ABORT
+ ?FAIL
end,
%% Identify .beam files NOT list in the .app, but present in ebin/
@@ -181,7 +181,7 @@ validate_modules(AppName, Mods) ->
M <- MissingMods]),
?ERROR("One or more .beam files exist that are not "
"listed in ~p.app:\n~s", [AppName, Msg2]),
- ?ABORT
+ ?FAIL
end.
ebin_modules() ->
diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl
index 22acff6..91b2cac 100644
--- a/src/rebar_port_compiler.erl
+++ b/src/rebar_port_compiler.erl
@@ -86,37 +86,21 @@
%% "$CFLAGS -X86Options"}]}
%%
-%% TODO: reconsider keeping both sources and objects once
-%% support for deprecated options has been remove.
-%% remove [] as valid value for sources, objects, and opts
-%% when removing deprecated options.
-record(spec, {type::'drv' | 'exe',
target::file:filename(),
- sources = [] :: [file:filename(), ...] | [],
- objects = [] :: [file:filename(), ...] | [],
+ sources = [] :: [file:filename(), ...],
+ objects = [] :: [file:filename(), ...],
opts = [] ::list() | []}).
-compile(Config, AppFile) ->
- rebar_utils:deprecated(port_sources, port_specs, Config, "soon"),
- rebar_utils:deprecated(so_name, port_specs, Config, "soon"),
- rebar_utils:deprecated(so_specs, port_specs, Config, "soon"),
-
- %% TODO: remove SpecType and OldSources make get_specs/2
- %% return list(#spec{}) when removing deprecated options
- {SpecType, {OldSources, Specs}} = get_specs(Config, AppFile),
-
- case {SpecType, OldSources, Specs} of
- {old, [], _} ->
- ok; % old specs empty
- {new, [], []} ->
- ok; % port_specs empty
-
- _ -> % have old/new specs
-
+compile(Config, _AppFile) ->
+ case get_specs(Config) of
+ [] ->
+ ok;
+ Specs ->
SharedEnv = rebar_config:get_env(Config, ?MODULE),
%% Compile each of the sources
- NewBins = compile_sources(OldSources, Specs, SharedEnv),
+ NewBins = compile_sources(Config, Specs, SharedEnv),
%% Make sure that the target directories exist
?INFO("Using specs ~p\n", [Specs]),
@@ -146,24 +130,17 @@ compile(Config, AppFile) ->
end, Specs)
end.
-clean(Config, AppFile) ->
- %% TODO: remove SpecType and OldSources make get_specs/2
- %% return list(#spec{}) when removing deprecated options
- {SpecType, {OldSources, Specs}} = get_specs(Config, AppFile),
-
- case {SpecType, OldSources, Specs} of
- {old, [], _} ->
- ok; % old specs empty
- {new, [], []} ->
- ok; % port_specs empty
-
- _ -> % have old/new specs
-
+clean(Config, _AppFile) ->
+ case get_specs(Config) of
+ [] ->
+ ok;
+ Specs ->
lists:foreach(fun(#spec{target=Target, objects=Objects}) ->
rebar_file_utils:delete_each([Target]),
rebar_file_utils:delete_each(Objects)
end, Specs)
- end.
+ end,
+ ok.
setup_env(Config) ->
setup_env(Config, []).
@@ -177,15 +154,17 @@ setup_env(Config, ExtraEnv) ->
%% merge with the default for this operating system. This enables
%% max flexibility for users.
DefaultEnv = filter_env(default_env(), []),
- PortEnv = filter_env(port_env(Config), []),
- OverrideEnv = global_defines() ++ PortEnv ++ filter_env(ExtraEnv, []),
+ RawPortEnv = rebar_config:get_list(Config, port_env, []),
+ PortEnv = filter_env(RawPortEnv, []),
+ Defines = get_defines(Config),
+ OverrideEnv = Defines ++ PortEnv ++ filter_env(ExtraEnv, []),
RawEnv = apply_defaults(os_env(), DefaultEnv) ++ OverrideEnv,
expand_vars_loop(merge_each_var(RawEnv, [])).
-global_defines() ->
- Defines = rebar_config:get_global(defines, []),
- Flags = string:join(["-D" ++ D || D <- Defines], " "),
- [{"ERL_CFLAGS", "$ERL_CFLAGS " ++ Flags}].
+get_defines(Config) ->
+ RawDefines = rebar_config:get_xconf(Config, defines, []),
+ Defines = string:join(["-D" ++ D || D <- RawDefines], " "),
+ [{"ERL_CFLAGS", "$ERL_CFLAGS " ++ Defines}].
replace_extension(File, NewExt) ->
OldExt = filename:extension(File),
@@ -198,18 +177,16 @@ replace_extension(File, OldExt, NewExt) ->
%% == compile and link ==
%%
-compile_sources([], Specs, SharedEnv) -> % port_spec
+compile_sources(Config, Specs, SharedEnv) ->
lists:foldl(
fun(#spec{sources=Sources, type=Type, opts=Opts}, NewBins) ->
Env = proplists:get_value(env, Opts, SharedEnv),
- compile_each(Sources, Type, Env, NewBins)
- end, [], Specs);
-compile_sources(OldSources, _Specs, SharedEnv) -> % deprecated
- compile_each(OldSources, drv, SharedEnv, []).
+ compile_each(Config, Sources, Type, Env, NewBins)
+ end, [], Specs).
-compile_each([], _Type, _Env, NewBins) ->
+compile_each(_Config, [], _Type, _Env, NewBins) ->
lists:reverse(NewBins);
-compile_each([Source | Rest], Type, Env, NewBins) ->
+compile_each(Config, [Source | Rest], Type, Env, NewBins) ->
Ext = filename:extension(Source),
Bin = replace_extension(Source, Ext, ".o"),
case needs_compile(Source, Bin) of
@@ -217,22 +194,27 @@ compile_each([Source | Rest], Type, Env, NewBins) ->
Template = select_compile_template(Type, compiler(Ext)),
Cmd = expand_command(Template, Env, Source, Bin),
ShOpts = [{env, Env}, return_on_error, {use_stdout, false}],
- exec_compiler(Source, Cmd, ShOpts),
- compile_each(Rest, Type, Env, [Bin | NewBins]);
+ exec_compiler(Config, Source, Cmd, ShOpts),
+ compile_each(Config, Rest, Type, Env, [Bin | NewBins]);
false ->
?INFO("Skipping ~s\n", [Source]),
- compile_each(Rest, Type, Env, NewBins)
+ compile_each(Config, Rest, Type, Env, NewBins)
end.
-exec_compiler(Source, Cmd, ShOpts) ->
+exec_compiler(Config, Source, Cmd, ShOpts) ->
case rebar_utils:sh(Cmd, ShOpts) of
{error, {_RC, RawError}} ->
- AbsSource = filename:absname(Source),
+ AbsSource = case rebar_utils:processing_base_dir(Config) of
+ true ->
+ Source;
+ false ->
+ filename:absname(Source)
+ end,
?CONSOLE("Compiling ~s\n", [AbsSource]),
Error = re:replace(RawError, Source, AbsSource,
[{return, list}, global]),
?CONSOLE("~s", [Error]),
- ?ABORT;
+ ?FAIL;
{ok, Output} ->
?CONSOLE("Compiling ~s\n", [Source]),
?CONSOLE("~s", [Output])
@@ -260,19 +242,11 @@ needs_link(SoName, NewBins) ->
%% == port_specs ==
%%
-get_specs(Config, AppFile) ->
- case rebar_config:get_local(Config, port_specs, undefined) of
- undefined ->
- %% TODO: DEPRECATED: remove support for non-port_specs syntax
- {old, old_get_specs(Config, AppFile)};
- PortSpecs ->
- {new, get_port_specs(Config, PortSpecs)}
- end.
-
-get_port_specs(Config, PortSpecs) ->
+get_specs(Config) ->
+ PortSpecs = rebar_config:get_local(Config, port_specs, []),
Filtered = filter_port_specs(PortSpecs),
OsType = os:type(),
- {[], [get_port_spec(Config, OsType, Spec) || Spec <- Filtered]}.
+ [get_port_spec(Config, OsType, Spec) || Spec <- Filtered].
filter_port_specs(Specs) ->
[S || S <- Specs, filter_port_spec(S)].
@@ -323,58 +297,6 @@ switch_to_dll_or_exe(Target) ->
_Other -> Target
end.
-%% TODO: DEPRECATED: remove support for non-port_specs syntax [old_*()]
-old_get_specs(Config, AppFile) ->
- OsType = os:type(),
- SourceFiles = old_get_sources(Config),
- Specs =
- case rebar_config:get_local(Config, so_specs, undefined) of
- undefined ->
- Objects = port_objects(SourceFiles),
- %% New form of so_specs is not provided. See if the old form
- %% of {so_name} is available instead
- Dir = "priv",
- SoName =
- case rebar_config:get_local(Config, so_name, undefined) of
- undefined ->
- %% Ok, neither old nor new form is
- %% available. Use the app name and
- %% generate a sensible default.
- AppName = rebar_app_utils:app_name(AppFile),
- DrvName = ?FMT("~s_drv.so", [AppName]),
- filename:join([Dir, DrvName]);
- AName ->
- %% Old form is available -- use it
- filename:join(Dir, AName)
- end,
- [old_get_so_spec({SoName, Objects}, OsType)];
- SoSpecs ->
- [old_get_so_spec(S, OsType) || S <- SoSpecs]
- end,
- {SourceFiles, Specs}.
-
-old_get_sources(Config) ->
- RawSources = rebar_config:get_local(Config, port_sources,
- ["c_src/*.c"]),
- FilteredSources = old_filter_port_sources(RawSources),
- old_expand_sources(FilteredSources).
-
-old_filter_port_sources(PortSources) ->
- [S || S <- PortSources, old_is_arch_port_sources(S)].
-
-old_is_arch_port_sources({Arch, _Sources}) -> rebar_utils:is_arch(Arch);
-old_is_arch_port_sources(_Sources) -> true.
-
-old_expand_sources(Sources) ->
- lists:flatmap(fun filelib:wildcard/1, Sources).
-
-old_get_so_spec({Target, Objects}, OsType) ->
- #spec{type=drv,
- target=maybe_switch_extension(OsType, Target),
- sources=[],
- objects=Objects,
- opts=[]}.
-
%%
%% == port_env ==
%%
@@ -498,35 +420,6 @@ is_expandable(InStr) ->
nomatch -> false
end.
-port_env(Config) ->
- %% TODO: remove support for deprecated port_envs option
- PortEnv = rebar_utils:get_deprecated_list(Config, port_envs, port_env,
- [], "soon"),
- %% TODO: remove migration of deprecated port_env DRV_-/EXE_-less vars
- %% when the deprecation grace period ends
- WarnAndConvertVar = fun(Var) ->
- New = "DRV_" ++ Var,
- rebar_utils:deprecated(Var, New, "soon"),
- New
- end,
- ConvertVar = fun(Var="CXX_TEMPLATE") -> WarnAndConvertVar(Var);
- (Var="CC_TEMPLATE") -> WarnAndConvertVar(Var);
- (Var="LINK_TEMPLATE") -> WarnAndConvertVar(Var);
- (Var) -> Var
- end,
- %% Also warn about references to deprecated vars? omitted for
- %% performance reasons.
- ReplaceVars = fun(Val) ->
- re:replace(Val, "\\$(CXX|CC|LINK)(_TEMPLATE)",
- "DRV_\\1\\2", [{return,list}, global])
- end,
- Convert = fun({ArchRegex, Var, Val}) ->
- {ArchRegex, ConvertVar(Var), ReplaceVars(Val)};
- ({Var, Val}) ->
- {ConvertVar(Var), ReplaceVars(Val)}
- end,
- [Convert(EnvVar) || EnvVar <- PortEnv].
-
%%
%% Filter a list of env vars such that only those which match the provided
%% architecture regex (or do not have a regex) are returned.
@@ -626,13 +519,32 @@ default_env() ->
{"darwin9.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"},
{"darwin9.*-64$", "LDFLAGS", "-arch x86_64 $LDFLAGS"},
- %% OS X Snow Leopard flags for 32-bit
- {"darwin10.*-32", "CFLAGS", "-m32 $CFLAGS"},
- {"darwin10.*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
- {"darwin10.*-32", "LDFLAGS", "-arch i386 $LDFLAGS"},
-
- %% OS X Lion flags for 32-bit
- {"darwin11.*-32", "CFLAGS", "-m32 $CFLAGS"},
- {"darwin11.*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
- {"darwin11.*-32", "LDFLAGS", "-arch i386 $LDFLAGS"}
+ %% OS X Snow Leopard, Lion, and Mountain Lion flags for 32-bit
+ {"darwin1[0-2].*-32", "CFLAGS", "-m32 $CFLAGS"},
+ {"darwin1[0-2].*-32", "CXXFLAGS", "-m32 $CXXFLAGS"},
+ {"darwin1[0-2].*-32", "LDFLAGS", "-arch i386 $LDFLAGS"},
+
+ %% Windows specific flags
+ %% add MS Visual C++ support to rebar on Windows
+ {"win32", "CC", "cl.exe"},
+ {"win32", "CXX", "cl.exe"},
+ {"win32", "LINKER", "link.exe"},
+ {"win32", "DRV_CXX_TEMPLATE",
+ %% DRV_* and EXE_* Templates are identical
+ "$CXX /c $CXXFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"},
+ {"win32", "DRV_CC_TEMPLATE",
+ "$CC /c $CFLAGS $DRV_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"},
+ {"win32", "DRV_LINK_TEMPLATE",
+ "$LINKER $PORT_IN_FILES $LDFLAGS $DRV_LDFLAGS /OUT:$PORT_OUT_FILE"},
+ %% DRV_* and EXE_* Templates are identical
+ {"win32", "EXE_CXX_TEMPLATE",
+ "$CXX /c $CXXFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"},
+ {"win32", "EXE_CC_TEMPLATE",
+ "$CC /c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES /Fo$PORT_OUT_FILE"},
+ {"win32", "EXE_LINK_TEMPLATE",
+ "$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"},
+ %% ERL_CFLAGS are ok as -I even though strictly it should be /I
+ {"win32", "ERL_LDFLAGS", " /LIBPATH:$ERL_EI_LIBDIR erl_interface.lib ei.lib"},
+ {"win32", "DRV_CFLAGS", "/Zi /Wall $ERL_CFLAGS"},
+ {"win32", "DRV_LDFLAGS", "/DLL $ERL_LDFLAGS"}
].
diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl
index ea34d4f..7ef58d6 100644
--- a/src/rebar_protobuffs_compiler.erl
+++ b/src/rebar_protobuffs_compiler.erl
@@ -35,7 +35,7 @@
%% Public API
%% ===================================================================
-compile(_Config, _AppFile) ->
+compile(Config, _AppFile) ->
case rebar_utils:find_files("src", ".*\\.proto$") of
[] ->
ok;
@@ -49,11 +49,11 @@ compile(_Config, _AppFile) ->
Proto <- FoundFiles],
%% Compile each proto file
- compile_each(Targets);
+ compile_each(Config, Targets);
false ->
?ERROR("Protobuffs library not present in code path!\n",
[]),
- ?ABORT
+ ?FAIL
end
end.
@@ -95,13 +95,15 @@ needs_compile(Proto, Beam) ->
ActualBeam = filename:join(["ebin", filename:basename(Beam)]),
filelib:last_modified(ActualBeam) < filelib:last_modified(Proto).
-compile_each([]) ->
+compile_each(_, []) ->
ok;
-compile_each([{Proto, Beam, Hrl} | Rest]) ->
+compile_each(Config, [{Proto, Beam, Hrl} | Rest]) ->
case needs_compile(Proto, Beam) of
true ->
?CONSOLE("Compiling ~s\n", [Proto]),
- case protobuffs_compile:scan_file(Proto) of
+ ErlOpts = rebar_utils:erl_opts(Config),
+ case protobuffs_compile:scan_file(Proto,
+ [{compile_flags,ErlOpts}]) of
ok ->
%% Compilation worked, but we need to move the
%% beam and .hrl file into the ebin/ and include/
@@ -115,12 +117,12 @@ compile_each([{Proto, Beam, Hrl} | Rest]) ->
Other ->
?ERROR("Protobuff compile of ~s failed: ~p\n",
[Proto, Other]),
- ?ABORT
+ ?FAIL
end;
false ->
ok
end,
- compile_each(Rest).
+ compile_each(Config, Rest).
delete_each([]) ->
ok;
diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl
new file mode 100644
index 0000000..5784f7d
--- /dev/null
+++ b/src/rebar_qc.erl
@@ -0,0 +1,167 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2011-2012 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(rebar_qc).
+
+-export([qc/2, triq/2, eqc/2]).
+
+-include("rebar.hrl").
+
+-define(QC_DIR, ".qc").
+
+%% ===================================================================
+%% Public API
+%% ===================================================================
+
+qc(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'qc' command~n", []),
+ run_qc(Config, qc_opts(Config)).
+
+triq(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'triq' command~n", []),
+ ok = load_qc_mod(triq),
+ run_qc(Config, qc_opts(Config), triq).
+
+eqc(Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'eqc' command~n", []),
+ ok = load_qc_mod(eqc),
+ run_qc(Config, qc_opts(Config), eqc).
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+-define(TRIQ_MOD, triq).
+-define(EQC_MOD, eqc).
+
+qc_opts(Config) ->
+ rebar_config:get(Config, qc_opts, []).
+
+run_qc(Config, QCOpts) ->
+ run_qc(Config, QCOpts, select_qc_mod(QCOpts)).
+
+run_qc(Config, RawQCOpts, QC) ->
+ ?DEBUG("Selected QC module: ~p~n", [QC]),
+ QCOpts = lists:filter(fun({qc_mod, _}) -> false;
+ (_) -> true
+ end, RawQCOpts),
+ run(Config, QC, QCOpts).
+
+select_qc_mod(QCOpts) ->
+ case proplists:get_value(qc_mod, QCOpts) of
+ undefined ->
+ detect_qc_mod();
+ QC ->
+ case code:ensure_loaded(QC) of
+ {module, QC} ->
+ QC;
+ {error, nofile} ->
+ ?ABORT("Configured QC library '~p' not available~n", [QC])
+ end
+ end.
+
+detect_qc_mod() ->
+ case code:ensure_loaded(?TRIQ_MOD) of
+ {module, ?TRIQ_MOD} ->
+ ?TRIQ_MOD;
+ {error, nofile} ->
+ case code:ensure_loaded(?EQC_MOD) of
+ {module, ?EQC_MOD} ->
+ ?EQC_MOD;
+ {error, nofile} ->
+ ?ABORT("No QC library available~n", [])
+ end
+ end.
+
+load_qc_mod(Mod) ->
+ case code:ensure_loaded(Mod) of
+ {module, Mod} ->
+ ok;
+ {error, nofile} ->
+ ?ABORT("Failed to load QC lib '~p'~n", [Mod])
+ end.
+
+ensure_dirs() ->
+ ok = filelib:ensure_dir(filename:join(qc_dir(), "dummy")),
+ ok = filelib:ensure_dir(filename:join(rebar_utils:ebin_dir(), "dummy")).
+
+setup_codepath() ->
+ CodePath = code:get_path(),
+ true = code:add_patha(qc_dir()),
+ true = code:add_pathz(rebar_utils:ebin_dir()),
+ CodePath.
+
+qc_dir() ->
+ filename:join(rebar_utils:get_cwd(), ?QC_DIR).
+
+run(Config, QC, QCOpts) ->
+ ?DEBUG("qc_opts: ~p~n", [QCOpts]),
+
+ ok = ensure_dirs(),
+ CodePath = setup_codepath(),
+
+ CompileOnly = rebar_utils:get_experimental_global(Config, compile_only,
+ false),
+ %% Compile erlang code to ?QC_DIR, using a tweaked config
+ %% with appropriate defines, and include all the test modules
+ %% as well.
+ {ok, _SrcErls} = rebar_erlc_compiler:test_compile(Config, "qc", ?QC_DIR),
+
+ case CompileOnly of
+ "true" ->
+ true = code:set_path(CodePath),
+ ?CONSOLE("Compiled modules for qc~n", []);
+ false ->
+ run1(QC, QCOpts, CodePath)
+ end.
+
+run1(QC, QCOpts, CodePath) ->
+ TestModule = fun(M) -> qc_module(QC, QCOpts, M) end,
+ case lists:flatmap(TestModule, find_prop_mods()) of
+ [] ->
+ true = code:set_path(CodePath),
+ ok;
+ Errors ->
+ ?ABORT("One or more QC properties didn't hold true:~n~p~n",
+ [Errors])
+ end.
+
+qc_module(QC=triq, _QCOpts, M) ->
+ case QC:module(M) of
+ true ->
+ [];
+ Failed ->
+ [Failed]
+ end;
+qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
+
+find_prop_mods() ->
+ Beams = rebar_utils:find_files(?QC_DIR, ".*\\.beam\$"),
+ [M || M <- [rebar_utils:erl_to_mod(Beam) || Beam <- Beams], has_prop(M)].
+
+has_prop(Mod) ->
+ lists:any(fun({F,_A}) -> lists:prefix("prop_", atom_to_list(F)) end,
+ Mod:module_info(exports)).
diff --git a/src/rebar_rel_utils.erl b/src/rebar_rel_utils.erl
index e502743..085dbd9 100644
--- a/src/rebar_rel_utils.erl
+++ b/src/rebar_rel_utils.erl
@@ -33,13 +33,13 @@
get_rel_release_info/2,
get_rel_apps/1,
get_rel_apps/2,
- get_previous_release_path/0,
+ get_previous_release_path/1,
get_rel_file_path/2,
- load_config/1,
+ load_config/2,
get_sys_tuple/1,
- get_target_dir/1,
- get_root_dir/1,
- get_target_parent_dir/1]).
+ get_target_dir/2,
+ get_root_dir/2,
+ get_target_parent_dir/2]).
-include("rebar.hrl").
@@ -107,12 +107,11 @@ get_rel_apps(Name, Path) ->
get_rel_file_path(Name, Path) ->
[RelFile] = filelib:wildcard(filename:join([Path, "releases", "*",
Name ++ ".rel"])),
- [BinDir|_] = re:replace(RelFile, Name ++ "\\.rel", ""),
- filename:join([binary_to_list(BinDir), Name ++ ".rel"]).
+ RelFile.
%% Get the previous release path from a global variable
-get_previous_release_path() ->
- case rebar_config:get_global(previous_release, false) of
+get_previous_release_path(Config) ->
+ case rebar_config:get_global(Config, previous_release, false) of
false ->
?ABORT("previous_release=PATH is required to "
"create upgrade package~n", []);
@@ -123,10 +122,10 @@ get_previous_release_path() ->
%%
%% Load terms from reltool.config
%%
-load_config(ReltoolFile) ->
+load_config(Config, ReltoolFile) ->
case rebar_config:consult_file(ReltoolFile) of
{ok, Terms} ->
- expand_version(Terms, filename:dirname(ReltoolFile));
+ expand_version(Config, Terms, filename:dirname(ReltoolFile));
Other ->
?ABORT("Failed to load expected config from ~s: ~p\n",
[ReltoolFile, Other])
@@ -148,8 +147,8 @@ get_sys_tuple(ReltoolConfig) ->
%% Look for {target_dir, TargetDir} in the reltool config file; if none is
%% found, use the name of the release as the default target directory.
%%
-get_target_dir(ReltoolConfig) ->
- case rebar_config:get_global(target_dir, undefined) of
+get_target_dir(Config, ReltoolConfig) ->
+ case rebar_config:get_global(Config, target_dir, undefined) of
undefined ->
case lists:keyfind(target_dir, 1, ReltoolConfig) of
{target_dir, TargetDir} ->
@@ -167,8 +166,8 @@ get_target_dir(ReltoolConfig) ->
filename:absname(TargetDir)
end.
-get_target_parent_dir(ReltoolConfig) ->
- TargetDir = get_target_dir(ReltoolConfig),
+get_target_parent_dir(Config, ReltoolConfig) ->
+ TargetDir = get_target_dir(Config, ReltoolConfig),
case lists:reverse(tl(lists:reverse(filename:split(TargetDir)))) of
[] -> ".";
Components -> filename:join(Components)
@@ -178,10 +177,10 @@ get_target_parent_dir(ReltoolConfig) ->
%% Look for root_dir in sys tuple and command line; fall back to
%% code:root_dir().
%%
-get_root_dir(ReltoolConfig) ->
+get_root_dir(Config, ReltoolConfig) ->
{sys, SysInfo} = get_sys_tuple(ReltoolConfig),
SysRootDirTuple = lists:keyfind(root_dir, 1, SysInfo),
- CmdRootDir = rebar_config:get_global(root_dir, undefined),
+ CmdRootDir = rebar_config:get_global(Config, root_dir, undefined),
case {SysRootDirTuple, CmdRootDir} of
%% root_dir in sys typle and no root_dir on cmd-line
{{root_dir, SysRootDir}, undefined} ->
@@ -217,16 +216,23 @@ make_proplist([H|T], Acc) ->
make_proplist([], Acc) ->
Acc.
-expand_version(ReltoolConfig, Dir) ->
+expand_version(Config, ReltoolConfig, Dir) ->
case lists:keyfind(sys, 1, ReltoolConfig) of
{sys, Sys} ->
- ExpandedSys = {sys, [expand_rel_version(Term, Dir) || Term <- Sys]},
- lists:keyreplace(sys, 1, ReltoolConfig, ExpandedSys);
+ {Config1, Rels} =
+ lists:foldl(
+ fun(Term, {C, R}) ->
+ {C1, Rel} = expand_rel_version(C, Term, Dir),
+ {C1, [Rel|R]}
+ end, {Config, []}, Sys),
+ ExpandedSys = {sys, lists:reverse(Rels)},
+ {Config1, lists:keyreplace(sys, 1, ReltoolConfig, ExpandedSys)};
_ ->
- ReltoolConfig
+ {Config, ReltoolConfig}
end.
-expand_rel_version({rel, Name, Version, Apps}, Dir) ->
- {rel, Name, rebar_utils:vcs_vsn(Version, Dir), Apps};
-expand_rel_version(Other, _Dir) ->
- Other.
+expand_rel_version(Config, {rel, Name, Version, Apps}, Dir) ->
+ {NewConfig, VsnString} = rebar_utils:vcs_vsn(Config, Version, Dir),
+ {NewConfig, {rel, Name, VsnString, Apps}};
+expand_rel_version(Config, Other, _Dir) ->
+ {Config, Other}.
diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl
index cf817e8..3c9b728 100644
--- a/src/rebar_reltool.erl
+++ b/src/rebar_reltool.erl
@@ -37,12 +37,12 @@
%% Public API
%% ===================================================================
-generate(Config, ReltoolFile) ->
+generate(Config0, ReltoolFile) ->
%% Make sure we have decent version of reltool available
check_vsn(),
%% Load the reltool configuration from the file
- ReltoolConfig = rebar_rel_utils:load_config(ReltoolFile),
+ {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile),
Sys = rebar_rel_utils:get_sys_tuple(ReltoolConfig),
@@ -56,32 +56,33 @@ generate(Config, ReltoolFile) ->
%% Finally, run reltool
case catch(run_reltool(Server, Config, ReltoolConfig)) of
ok ->
- ok;
+ {ok, Config};
{error, failed} ->
- ?ABORT;
+ ?FAIL;
Other2 ->
?ERROR("Unexpected error: ~p\n", [Other2]),
- ?ABORT
+ ?FAIL
end.
-overlay(_Config, ReltoolFile) ->
+overlay(Config, ReltoolFile) ->
%% Load the reltool configuration from the file
- ReltoolConfig = rebar_rel_utils:load_config(ReltoolFile),
- process_overlay(ReltoolConfig).
+ {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
+ {process_overlay(Config, ReltoolConfig), Config1}.
-clean(_Config, ReltoolFile) ->
- ReltoolConfig = rebar_rel_utils:load_config(ReltoolFile),
- TargetDir = rebar_rel_utils:get_target_dir(ReltoolConfig),
+clean(Config, ReltoolFile) ->
+ {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
+ TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
rebar_file_utils:rm_rf(TargetDir),
- rebar_file_utils:delete_each(["reltool.spec"]).
-
-
+ rebar_file_utils:delete_each(["reltool.spec"]),
+ {ok, Config1}.
%% ===================================================================
%% Internal functions
%% ===================================================================
check_vsn() ->
+ %% TODO: use application:load and application:get_key once we require
+ %% R14A or newer. There's no reltool.app before R14A.
case code:lib_dir(reltool) of
{error, bad_name} ->
?ABORT("Reltool support requires the reltool application "
@@ -97,8 +98,8 @@ check_vsn() ->
end
end.
-process_overlay(ReltoolConfig) ->
- TargetDir = rebar_rel_utils:get_target_dir(ReltoolConfig),
+process_overlay(Config, ReltoolConfig) ->
+ TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
{_BootRelName, BootRelVsn} =
rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
@@ -108,10 +109,11 @@ process_overlay(ReltoolConfig) ->
OverlayVars0 =
dict:from_list([{erts_vsn, "erts-" ++ erlang:system_info(version)},
{rel_vsn, BootRelVsn},
- {target_dir, TargetDir}]),
+ {target_dir, TargetDir},
+ {hostname, net_adm:localhost()}]),
%% Load up any variables specified by overlay_vars
- OverlayVars1 = overlay_vars(OverlayVars0, ReltoolConfig),
+ OverlayVars1 = overlay_vars(Config, OverlayVars0, ReltoolConfig),
OverlayVars = rebar_templater:resolve_variables(dict:to_list(OverlayVars1),
OverlayVars1),
@@ -134,9 +136,11 @@ process_overlay(ReltoolConfig) ->
%% variable in the file from reltool.config and then override that value by
%% providing an additional file on the command-line.
%%
-overlay_vars(Vars0, ReltoolConfig) ->
+overlay_vars(Config, Vars0, ReltoolConfig) ->
BaseVars = load_vars_file(proplists:get_value(overlay_vars, ReltoolConfig)),
- OverrideVars = load_vars_file(rebar_config:get_global(overlay_vars, undefined)),
+ OverrideVars = load_vars_file(rebar_config:get_global(Config,
+ overlay_vars,
+ undefined)),
M = fun(_Key, _Base, Override) -> Override end,
dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars).
@@ -146,15 +150,13 @@ overlay_vars(Vars0, ReltoolConfig) ->
load_vars_file(undefined) ->
dict:new();
load_vars_file(File) ->
- case file:consult(File) of
+ case rebar_config:consult_file(File) of
{ok, Terms} ->
dict:from_list(Terms);
{error, Reason} ->
- ?ABORT("Unable to load overlay_vars from ~s: ~p\n", [File, Reason])
+ ?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason])
end.
-
-
validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) ->
case lists:keyfind(rel, 1, ReltoolConfig) of
false ->
@@ -187,19 +189,18 @@ app_exists(App, Server) when is_atom(App) ->
app_exists(AppTuple, Server) when is_tuple(AppTuple) ->
app_exists(element(1, AppTuple), Server).
-
-run_reltool(Server, _Config, ReltoolConfig) ->
+run_reltool(Server, Config, ReltoolConfig) ->
case reltool:get_target_spec(Server) of
{ok, Spec} ->
%% Pull the target dir and make sure it exists
- TargetDir = rebar_rel_utils:get_target_dir(ReltoolConfig),
- mk_target_dir(TargetDir),
+ TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
+ mk_target_dir(Config, TargetDir),
%% Determine the otp root dir to use
- RootDir = rebar_rel_utils:get_root_dir(ReltoolConfig),
+ RootDir = rebar_rel_utils:get_root_dir(Config, ReltoolConfig),
%% Dump the spec, if necessary
- dump_spec(Spec),
+ dump_spec(Config, Spec),
%% Have reltool actually run
case reltool:eval_target_spec(Spec, RootDir, TargetDir) of
@@ -215,37 +216,35 @@ run_reltool(Server, _Config, ReltoolConfig) ->
ok = create_RELEASES(TargetDir, BootRelName, BootRelVsn),
- process_overlay(ReltoolConfig);
+ process_overlay(Config, ReltoolConfig);
{error, Reason} ->
?ABORT("Unable to generate spec: ~s\n", [Reason])
end.
-
-mk_target_dir(TargetDir) ->
+mk_target_dir(Config, TargetDir) ->
case filelib:ensure_dir(filename:join(TargetDir, "dummy")) of
ok ->
ok;
{error, eexist} ->
%% Output directory already exists; if force=1, wipe it out
- case rebar_config:get_global(force, "0") of
+ case rebar_config:get_global(Config, force, "0") of
"1" ->
rebar_file_utils:rm_rf(TargetDir),
ok = file:make_dir(TargetDir);
_ ->
?ERROR("Release target directory ~p already exists!\n",
[TargetDir]),
- ?ABORT
+ ?FAIL
end;
{error, Reason} ->
?ERROR("Failed to make target dir ~p: ~s\n",
[TargetDir, file:format_error(Reason)]),
- ?ABORT
+ ?FAIL
end.
-
-dump_spec(Spec) ->
- case rebar_config:get_global(dump_spec, "0") of
+dump_spec(Config, Spec) ->
+ case rebar_config:get_global(Config, dump_spec, "0") of
"1" ->
SpecBin = list_to_binary(io_lib:print(Spec, 1, 120, -1)),
ok = file:write_file("reltool.spec", SpecBin);
@@ -346,10 +345,22 @@ apply_file_info(InFile, OutFile) ->
create_RELEASES(TargetDir, RelName, RelVsn) ->
ReleasesDir = filename:join(TargetDir, "releases"),
+ RelFile = filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
+ Apps = rebar_rel_utils:get_rel_apps(RelFile),
+ TargetLib = filename:join(TargetDir,"lib"),
+
+ AppDirs =
+ [ {App, Vsn, TargetLib}
+ || {App, Vsn} <- Apps,
+ filelib:is_dir(
+ filename:join(TargetLib,
+ lists:concat([App, "-", Vsn]))) ],
+
case release_handler:create_RELEASES(
- TargetDir, ReleasesDir,
- filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
- filename:join(TargetDir, "lib")) of
+ code:root_dir(),
+ ReleasesDir,
+ RelFile,
+ AppDirs) of
ok ->
ok;
{error, Reason} ->
diff --git a/src/rebar_shell.erl b/src/rebar_shell.erl
new file mode 100644
index 0000000..2dbf4a0
--- /dev/null
+++ b/src/rebar_shell.erl
@@ -0,0 +1,56 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% rebar: Erlang Build Tools
+%%
+%% Copyright (c) 2011 Trifork
+%%
+%% 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_shell).
+-author("Kresten Krab Thorup <krab@trifork.com>").
+
+-include("rebar.hrl").
+
+-export([shell/2]).
+
+shell(_Config, _AppFile) ->
+ ?CONSOLE("NOTICE: Using experimental 'shell' command~n", []),
+ %% backwards way to say we only want this executed
+ %% for the "top level" directory
+ case is_deps_dir(rebar_utils:get_cwd()) of
+ false ->
+ true = code:add_pathz(rebar_utils:ebin_dir()),
+ user_drv:start(),
+ %% this call never returns (until user quits shell)
+ shell:server(false, false);
+ true ->
+ ok
+ end,
+ ok.
+
+is_deps_dir(Dir) ->
+ case lists:reverse(filename:split(Dir)) of
+ [_, "deps" | _] ->
+ true;
+ _V ->
+ false
+ end.
diff --git a/src/rebar_subdirs.erl b/src/rebar_subdirs.erl
index 6c33441..f444a59 100644
--- a/src/rebar_subdirs.erl
+++ b/src/rebar_subdirs.erl
@@ -40,7 +40,7 @@ preprocess(Config, _) ->
Cwd = rebar_utils:get_cwd(),
ListSubdirs = rebar_config:get_local(Config, sub_dirs, []),
Subdirs0 = lists:flatmap(fun filelib:wildcard/1, ListSubdirs),
- case {rebar_core:is_skip_dir(Cwd), Subdirs0} of
+ case {rebar_config:is_skip_dir(Config, Cwd), Subdirs0} of
{true, []} ->
{ok, []};
{true, _} ->
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index 1e015ee..0e1eef1 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -43,49 +43,64 @@
%% Public API
%% ===================================================================
-'create-app'(Config, File) ->
+'create-app'(Config, _File) ->
%% Alias for create w/ template=simpleapp
- rebar_config:set_global(template, "simpleapp"),
- create(Config, File).
+ create1(Config, "simpleapp").
-'create-node'(Config, File) ->
+'create-node'(Config, _File) ->
%% Alias for create w/ template=simplenode
- rebar_config:set_global(template, "simplenode"),
- create(Config, File).
+ create1(Config, "simplenode").
-'list-templates'(_Config, _File) ->
- %% Load a list of all the files in the escript -- cache it in the pdict
- %% since we'll potentially need to walk it several times over the course
- %% of a run.
- cache_escript_files(),
+'list-templates'(Config, _File) ->
+ {AvailTemplates, Files} = find_templates(Config),
+ ?DEBUG("Available templates: ~p\n", [AvailTemplates]),
- %% Build a list of available templates
- AvailTemplates = find_disk_templates() ++ find_escript_templates(),
- ?CONSOLE("Available templates:\n", []),
- _ = [begin
- BaseName = filename:basename(F, ".template"),
- {ok, Template} = file:consult(F),
- {_, VarList} = lists:keyfind(variables, 1, Template),
- Vars = lists:foldl(fun({V,_}, Acc) ->
- [atom_to_list(V) | Acc]
- end, [], VarList),
- ?CONSOLE("\t* ~s: ~s (~p) (variables: ~p)\n",
- [BaseName, F, Type, string:join(Vars, ", ")])
- end || {Type, F} <- AvailTemplates],
+ lists:foreach(
+ fun({Type, F}) ->
+ BaseName = filename:basename(F, ".template"),
+ TemplateTerms = consult(load_file(Files, Type, F)),
+ {_, VarList} = lists:keyfind(variables, 1, TemplateTerms),
+ Vars = lists:foldl(fun({V,_}, Acc) ->
+ [atom_to_list(V) | Acc]
+ end, [], VarList),
+ ?CONSOLE(" * ~s: ~s (~p) (variables: ~p)\n",
+ [BaseName, F, Type, string:join(Vars, ", ")])
+ end, AvailTemplates),
ok.
+create(Config, _) ->
+ TemplateId = template_id(Config),
+ create1(Config, TemplateId).
-create(_Config, _) ->
- %% Load a list of all the files in the escript -- cache it in the pdict
- %% since we'll potentially need to walk it several times over the course
- %% of a run.
- cache_escript_files(),
+%%
+%% Given a list of key value pairs, for each string value attempt to
+%% render it using Dict as the context. Storing the result in Dict as Key.
+%%
+resolve_variables([], Dict) ->
+ Dict;
+resolve_variables([{Key, Value0} | Rest], Dict) when is_list(Value0) ->
+ Value = render(list_to_binary(Value0), Dict),
+ resolve_variables(Rest, dict:store(Key, Value, Dict));
+resolve_variables([_Pair | Rest], Dict) ->
+ resolve_variables(Rest, Dict).
- %% Build a list of available templates
- AvailTemplates = find_disk_templates() ++ find_escript_templates(),
- ?DEBUG("Available templates: ~p\n", [AvailTemplates]),
+%%
+%% Render a binary to a string, using mustache and the specified context
+%%
+render(Bin, Context) ->
+ %% Be sure to escape any double-quotes before rendering...
+ ReOpts = [global, {return, list}],
+ Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts),
+ Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts),
+ mustache:render(Str1, Context).
- TemplateId = template_id(),
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+create1(Config, TemplateId) ->
+ {AvailTemplates, Files} = find_templates(Config),
+ ?DEBUG("Available templates: ~p\n", [AvailTemplates]),
%% Using the specified template id, find the matching template file/type.
%% Note that if you define the same template in both ~/.rebar/templates
@@ -95,7 +110,7 @@ create(_Config, _) ->
%% Load the template definition as is and get the list of variables the
%% template requires.
- TemplateTerms = consult(load_file(Type, Template)),
+ TemplateTerms = consult(load_file(Files, Type, Template)),
case lists:keyfind(variables, 1, TemplateTerms) of
{variables, Vars} ->
case parse_vars(Vars, dict:new()) of
@@ -115,7 +130,7 @@ create(_Config, _) ->
end,
%% Load variables from disk file, if provided
- Context1 = case rebar_config:get_global(template_vars, undefined) of
+ Context1 = case rebar_config:get_global(Config, template_vars, undefined) of
undefined ->
Context0;
File ->
@@ -132,7 +147,7 @@ create(_Config, _) ->
%% For each variable, see if it's defined in global vars -- if it is,
%% prefer that value over the defaults
- Context2 = update_vars(dict:fetch_keys(Context1), Context1),
+ Context2 = update_vars(Config, dict:fetch_keys(Context1), Context1),
?DEBUG("Template ~p context: ~p\n", [TemplateId, dict:to_list(Context1)]),
%% Handle variables that possibly include other variables in their
@@ -144,76 +159,59 @@ create(_Config, _) ->
%% Now, use our context to process the template definition -- this
%% permits us to use variables within the definition for filenames.
- FinalTemplate = consult(render(load_file(Type, Template), Context)),
+ FinalTemplate = consult(render(load_file(Files, Type, Template), Context)),
?DEBUG("Final template def ~p: ~p\n", [TemplateId, FinalTemplate]),
%% Execute the instructions in the finalized template
- Force = rebar_config:get_global(force, "0"),
- execute_template(FinalTemplate, Type, Template, Context, Force, []).
-
-
-%%
-%% Given a list of key value pairs, for each string value attempt to
-%% render it using Dict as the context. Storing the result in Dict as Key.
-%%
-resolve_variables([], Dict) ->
- Dict;
-resolve_variables([{Key, Value0} | Rest], Dict) when is_list(Value0) ->
- Value = render(list_to_binary(Value0), Dict),
- resolve_variables(Rest, dict:store(Key, Value, Dict));
-resolve_variables([_Pair | Rest], Dict) ->
- resolve_variables(Rest, Dict).
-
+ Force = rebar_config:get_global(Config, force, "0"),
+ execute_template(Files, FinalTemplate, Type, Template, Context, Force, []).
-%%
-%% Render a binary to a string, using mustache and the specified context
-%%
-render(Bin, Context) ->
- %% Be sure to escape any double-quotes before rendering...
- ReOpts = [global, {return, list}],
- Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts),
- Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts),
- mustache:render(Str1, Context).
+find_templates(Config) ->
+ %% Load a list of all the files in the escript -- cache them since
+ %% we'll potentially need to walk it several times over the course of
+ %% a run.
+ Files = cache_escript_files(Config),
+ %% Build a list of available templates
+ AvailTemplates = find_disk_templates(Config)
+ ++ find_escript_templates(Files),
-%% ===================================================================
-%% Internal functions
-%% ===================================================================
+ {AvailTemplates, Files}.
%%
-%% Scan the current escript for available files and cache in pdict.
+%% Scan the current escript for available files
%%
-cache_escript_files() ->
+cache_escript_files(Config) ->
{ok, Files} = rebar_utils:escript_foldl(
fun(Name, _, GetBin, Acc) ->
[{Name, GetBin()} | Acc]
end,
- [], rebar_config:get_global(escript, undefined)),
- erlang:put(escript_files, Files).
+ [], rebar_config:get_xconf(Config, escript)),
+ Files.
-
-template_id() ->
- case rebar_config:get_global(template, undefined) of
+template_id(Config) ->
+ case rebar_config:get_global(Config, template, undefined) of
undefined ->
?ABORT("No template specified.\n", []);
TemplateId ->
TemplateId
end.
-find_escript_templates() ->
- [{escript, Name} || {Name, _Bin} <- erlang:get(escript_files),
- re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match].
+find_escript_templates(Files) ->
+ [{escript, Name}
+ || {Name, _Bin} <- Files,
+ re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match].
-find_disk_templates() ->
- OtherTemplates = find_other_templates(),
+find_disk_templates(Config) ->
+ OtherTemplates = find_other_templates(Config),
HomeFiles = rebar_utils:find_files(filename:join([os:getenv("HOME"),
".rebar", "templates"]),
?TEMPLATE_RE),
LocalFiles = rebar_utils:find_files(".", ?TEMPLATE_RE),
[{file, F} || F <- OtherTemplates ++ HomeFiles ++ LocalFiles].
-find_other_templates() ->
- case rebar_config:get_global(template_dir, undefined) of
+find_other_templates(Config) ->
+ case rebar_config:get_global(Config, template_dir, undefined) of
undefined ->
[];
TemplateDir ->
@@ -233,10 +231,10 @@ select_template([{Type, Avail} | Rest], Template) ->
%%
%% Read the contents of a file from the appropriate source
%%
-load_file(escript, Name) ->
- {Name, Bin} = lists:keyfind(Name, 1, erlang:get(escript_files)),
+load_file(Files, escript, Name) ->
+ {Name, Bin} = lists:keyfind(Name, 1, Files),
Bin;
-load_file(file, Name) ->
+load_file(_Files, file, Name) ->
{ok, Bin} = file:read_file(Name),
Bin.
@@ -256,15 +254,15 @@ parse_vars(Other, _Dict) ->
%% Given a list of keys in Dict, see if there is a corresponding value defined
%% in the global config; if there is, update the key in Dict with it
%%
-update_vars([], Dict) ->
+update_vars(_Config, [], Dict) ->
Dict;
-update_vars([Key | Rest], Dict) ->
- Value = rebar_config:get_global(Key, dict:fetch(Key, Dict)),
- update_vars(Rest, dict:store(Key, Value, Dict)).
+update_vars(Config, [Key | Rest], Dict) ->
+ Value = rebar_config:get_global(Config, Key, dict:fetch(Key, Dict)),
+ update_vars(Config, Rest, dict:store(Key, Value, Dict)).
%%
-%% Given a string or binary, parse it into a list of terms, ala file:consult/0
+%% Given a string or binary, parse it into a list of terms, ala file:consult/1
%%
consult(Str) when is_list(Str) ->
consult([], Str, []);
@@ -319,89 +317,91 @@ write_file(Output, Data, Force) ->
%%
%% Execute each instruction in a template definition file.
%%
-execute_template([], _TemplateType, _TemplateName, _Context,
- _Force, ExistingFiles) ->
+execute_template(_Files, [], _TemplateType, _TemplateName,
+ _Context, _Force, ExistingFiles) ->
case ExistingFiles of
[] ->
ok;
_ ->
Msg = lists:flatten([io_lib:format("\t* ~p~n", [F]) ||
F <- lists:reverse(ExistingFiles)]),
- Help =
- "To force overwriting, specify force=1 on the command line.\n",
+ Help = "To force overwriting, specify -f/--force/force=1"
+ " on the command line.\n",
?ERROR("One or more files already exist on disk and "
"were not generated:~n~s~s", [Msg , Help])
end;
-execute_template([{template, Input, Output} | Rest], TemplateType,
+execute_template(Files, [{template, Input, Output} | Rest], TemplateType,
TemplateName, Context, Force, ExistingFiles) ->
InputName = filename:join(filename:dirname(TemplateName), Input),
- case write_file(Output, render(load_file(TemplateType, InputName), Context),
- Force) of
+ File = load_file(Files, TemplateType, InputName),
+ case write_file(Output, render(File, Context), Force) of
ok ->
- execute_template(Rest, TemplateType, TemplateName, Context,
- Force, ExistingFiles);
+ execute_template(Files, Rest, TemplateType, TemplateName,
+ Context, Force, ExistingFiles);
{error, exists} ->
- execute_template(Rest, TemplateType, TemplateName, Context,
- Force, [Output|ExistingFiles])
+ execute_template(Files, Rest, TemplateType, TemplateName,
+ Context, Force, [Output|ExistingFiles])
end;
-execute_template([{file, Input, Output} | Rest], TemplateType, TemplateName,
- Context, Force, ExistingFiles) ->
+execute_template(Files, [{file, Input, Output} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
InputName = filename:join(filename:dirname(TemplateName), Input),
- case write_file(Output, load_file(TemplateType, InputName), Force) of
+ File = load_file(Files, TemplateType, InputName),
+ case write_file(Output, File, Force) of
ok ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles);
{error, exists} ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, [Output|ExistingFiles])
end;
-execute_template([{dir, Name} | Rest], TemplateType, TemplateName, Context,
- Force, ExistingFiles) ->
+execute_template(Files, [{dir, Name} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
case filelib:ensure_dir(filename:join(Name, "dummy")) of
ok ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles);
{error, Reason} ->
?ABORT("Failed while processing template instruction "
"{dir, ~s}: ~p\n", [Name, Reason])
end;
-execute_template([{copy, Input, Output} | Rest], TemplateType, TemplateName,
- Context, Force, ExistingFiles) ->
+execute_template(Files, [{copy, Input, Output} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
InputName = filename:join(filename:dirname(TemplateName), Input),
try rebar_file_utils:cp_r([InputName ++ "/*"], Output) of
ok ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles)
catch _:_ ->
?ABORT("Failed while processing template instruction "
"{copy, ~s, ~s}~n", [Input, Output])
end;
-execute_template([{chmod, Mod, File} | Rest], TemplateType, TemplateName,
- Context, Force, ExistingFiles) when is_integer(Mod) ->
+execute_template(Files, [{chmod, Mod, File} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles)
+ when is_integer(Mod) ->
case file:change_mode(File, Mod) of
ok ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles);
{error, Reason} ->
?ABORT("Failed while processing template instruction "
"{chmod, ~b, ~s}: ~p~n", [Mod, File, Reason])
end;
-execute_template([{symlink, Existing, New} | Rest], TemplateType, TemplateName,
- Context, Force, ExistingFiles) ->
+execute_template(Files, [{symlink, Existing, New} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
case file:make_symlink(Existing, New) of
ok ->
- execute_template(Rest, TemplateType, TemplateName,
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles);
{error, Reason} ->
?ABORT("Failed while processing template instruction "
"{symlink, ~s, ~s}: ~p~n", [Existing, New, Reason])
end;
-execute_template([{variables, _} | Rest], TemplateType, TemplateName, Context,
- Force, ExistingFiles) ->
- execute_template(Rest, TemplateType, TemplateName,
+execute_template(Files, [{variables, _} | Rest], TemplateType,
+ TemplateName, Context, Force, ExistingFiles) ->
+ execute_template(Files, Rest, TemplateType, TemplateName,
Context, Force, ExistingFiles);
-execute_template([Other | Rest], TemplateType, TemplateName, Context,
- Force, ExistingFiles) ->
+execute_template(Files, [Other | Rest], TemplateType, TemplateName,
+ Context, Force, ExistingFiles) ->
?WARN("Skipping unknown template instruction: ~p\n", [Other]),
- execute_template(Rest, TemplateType, TemplateName, Context,
+ execute_template(Files, Rest, TemplateType, TemplateName, Context,
Force, ExistingFiles).
diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl
index aaa24fd..14ea758 100644
--- a/src/rebar_upgrade.erl
+++ b/src/rebar_upgrade.erl
@@ -38,17 +38,19 @@
%% Public API
%% ====================================================================
-'generate-upgrade'(_Config, ReltoolFile) ->
+'generate-upgrade'(Config0, ReltoolFile) ->
%% Get the old release path
- ReltoolConfig = rebar_rel_utils:load_config(ReltoolFile),
- TargetParentDir = rebar_rel_utils:get_target_parent_dir(ReltoolConfig),
- TargetDir = rebar_rel_utils:get_target_dir(ReltoolConfig),
+ {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile),
+ TargetParentDir = rebar_rel_utils:get_target_parent_dir(Config,
+ ReltoolConfig),
+ TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
- OldVerPath = filename:join([TargetParentDir,
- rebar_rel_utils:get_previous_release_path()]),
+ PrevRelPath = rebar_rel_utils:get_previous_release_path(Config),
+ OldVerPath = filename:join([TargetParentDir, PrevRelPath]),
%% Run checks to make sure that building a package is possible
- {NewVerPath, NewName, NewVer} = run_checks(OldVerPath, ReltoolConfig),
+ {NewVerPath, NewName, NewVer} = run_checks(Config, OldVerPath,
+ ReltoolConfig),
NameVer = NewName ++ "_" ++ NewVer,
%% Save the code path prior to doing anything
@@ -72,13 +74,13 @@
%% Restore original path
true = code:set_path(OrigPath),
- ok.
+ {ok, Config}.
%% ===================================================================
%% Internal functions
%% ==================================================================
-run_checks(OldVerPath, ReltoolConfig) ->
+run_checks(Config, OldVerPath, ReltoolConfig) ->
true = rebar_utils:prop_check(filelib:is_dir(OldVerPath),
"Release directory doesn't exist (~p)~n",
[OldVerPath]),
@@ -86,8 +88,9 @@ run_checks(OldVerPath, ReltoolConfig) ->
{Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
NewVerPath =
- filename:join([rebar_rel_utils:get_target_parent_dir(ReltoolConfig),
- Name]),
+ filename:join(
+ [rebar_rel_utils:get_target_parent_dir(Config, ReltoolConfig),
+ Name]),
true = rebar_utils:prop_check(filelib:is_dir(NewVerPath),
"Release directory doesn't exist (~p)~n",
[NewVerPath]),
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index 27b2440..1049c1d 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -31,8 +31,7 @@
get_arch/0,
wordsize/0,
sh/2,
- find_files/2,
- find_files/3,
+ find_files/2, find_files/3,
now_str/0,
ensure_dir/1,
beam_to_mod/2, beams/1,
@@ -42,13 +41,18 @@
find_executable/1,
prop_check/3,
expand_code_path/0,
- deprecated/3, deprecated/4,
expand_env_variable/3,
- vcs_vsn/2,
- get_deprecated_global/3, get_deprecated_global/4,
+ vcs_vsn/3,
+ deprecated/3, deprecated/4,
+ get_deprecated_global/4, get_deprecated_global/5,
+ get_experimental_global/3, get_experimental_local/3,
get_deprecated_list/4, get_deprecated_list/5,
get_deprecated_local/4, get_deprecated_local/5,
- delayed_halt/1]).
+ delayed_halt/1,
+ erl_opts/1,
+ src_dirs/1,
+ ebin_dir/0,
+ processing_base_dir/1, processing_base_dir/2]).
-include("rebar.hrl").
@@ -71,7 +75,8 @@ is_arch(ArchRegex) ->
get_arch() ->
Words = wordsize(),
erlang:system_info(otp_release) ++ "-"
- ++ erlang:system_info(system_architecture) ++ "-" ++ Words.
+ ++ erlang:system_info(system_architecture) ++ "-" ++ Words
+ ++ "-" ++ os_family().
wordsize() ->
try erlang:system_info({wordsize, external}) of
@@ -171,7 +176,7 @@ prop_check(false, Msg, Args) -> ?ABORT(Msg, Args).
%% Convert all the entries in the code path to absolute paths.
expand_code_path() ->
- CodePath = lists:foldl(fun (Path, Acc) ->
+ CodePath = lists:foldl(fun(Path, Acc) ->
[filename:absname(Path) | Acc]
end, [], code:get_path()),
code:set_path(lists:reverse(CodePath)).
@@ -195,65 +200,31 @@ expand_env_variable(InStr, VarName, RawVarValue) ->
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
end.
-vcs_vsn(Vcs, Dir) ->
+vcs_vsn(Config, Vcs, Dir) ->
Key = {Vcs, Dir},
- try ets:lookup_element(rebar_vsn_cache, Key, 2)
- catch
- error:badarg ->
+ Cache = rebar_config:get_xconf(Config, vsn_cache),
+ case dict:find(Key, Cache) of
+ error ->
VsnString = vcs_vsn_1(Vcs, Dir),
- ets:insert(rebar_vsn_cache, {Key, VsnString}),
- VsnString
+ Cache1 = dict:store(Key, VsnString, Cache),
+ Config1 = rebar_config:set_xconf(Config, vsn_cache, Cache1),
+ {Config1, VsnString};
+ {ok, VsnString} ->
+ {Config, VsnString}
end.
-vcs_vsn_1(Vcs, Dir) ->
- case vcs_vsn_cmd(Vcs) of
- {unknown, VsnString} ->
- ?DEBUG("vcs_vsn: Unknown VCS atom in vsn field: ~p\n", [Vcs]),
- VsnString;
- {cmd, CmdString} ->
- vcs_vsn_invoke(CmdString, Dir);
- Cmd ->
- %% If there is a valid VCS directory in the application directory,
- %% use that version info
- Extension = lists:concat([".", Vcs]),
- case filelib:is_dir(filename:join(Dir, Extension)) of
- true ->
- ?DEBUG("vcs_vsn: Primary vcs used for ~s\n", [Dir]),
- vcs_vsn_invoke(Cmd, Dir);
- false ->
- %% No VCS directory found for the app. Depending on source
- %% tree structure, there may be one higher up, but that can
- %% yield unexpected results when used with deps. So, we
- %% fallback to searching for a priv/vsn.Vcs file.
- VsnFile = filename:join([Dir, "priv", "vsn" ++ Extension]),
- case file:read_file(VsnFile) of
- {ok, VsnBin} ->
- ?DEBUG("vcs_vsn: Read ~s from priv/vsn.~p\n",
- [VsnBin, Vcs]),
- string:strip(binary_to_list(VsnBin), right, $\n);
- {error, enoent} ->
- ?DEBUG("vcs_vsn: Fallback to vcs for ~s\n", [Dir]),
- vcs_vsn_invoke(Cmd, Dir)
- end
- end
- end.
+get_deprecated_global(Config, OldOpt, NewOpt, When) ->
+ get_deprecated_global(Config, OldOpt, NewOpt, undefined, When).
-get_deprecated_global(OldOpt, NewOpt, When) ->
- get_deprecated_global(OldOpt, NewOpt, undefined, When).
+get_deprecated_global(Config, OldOpt, NewOpt, Default, When) ->
+ get_deprecated_3(fun rebar_config:get_global/3,
+ Config, OldOpt, NewOpt, Default, When).
-get_deprecated_global(OldOpt, NewOpt, Default, When) ->
- case rebar_config:get_global(NewOpt, Default) of
- Default ->
- case rebar_config:get_global(OldOpt, Default) of
- Default ->
- Default;
- Old ->
- deprecated(OldOpt, NewOpt, When),
- Old
- end;
- New ->
- New
- end.
+get_experimental_global(Config, Opt, Default) ->
+ get_experimental_3(fun rebar_config:get_global/3, Config, Opt, Default).
+
+get_experimental_local(Config, Opt, Default) ->
+ get_experimental_3(fun rebar_config:get_local/3, Config, Opt, Default).
get_deprecated_list(Config, OldOpt, NewOpt, When) ->
get_deprecated_list(Config, OldOpt, NewOpt, undefined, When).
@@ -313,10 +284,44 @@ delayed_halt(Code) ->
end
end.
+%% @doc Return list of erl_opts
+-spec erl_opts(rebar_config:config()) -> list().
+erl_opts(Config) ->
+ RawErlOpts = filter_defines(rebar_config:get(Config, erl_opts, []), []),
+ Defines = [{d, list_to_atom(D)} ||
+ D <- rebar_config:get_xconf(Config, defines, [])],
+ Opts = Defines ++ RawErlOpts,
+ case proplists:is_defined(no_debug_info, Opts) of
+ true ->
+ [O || O <- Opts, O =/= no_debug_info];
+ false ->
+ [debug_info|Opts]
+ end.
+
+-spec src_dirs([string()]) -> [file:filename(), ...].
+src_dirs([]) ->
+ ["src"];
+src_dirs(SrcDirs) ->
+ SrcDirs.
+
+ebin_dir() ->
+ filename:join(get_cwd(), "ebin").
+
+processing_base_dir(Config) ->
+ Cwd = rebar_utils:get_cwd(),
+ processing_base_dir(Config, Cwd).
+
+processing_base_dir(Config, Dir) ->
+ Dir =:= rebar_config:get_xconf(Config, base_dir).
+
%% ====================================================================
%% Internal functions
%% ====================================================================
+os_family() ->
+ {OsFamily, _} = os:type(),
+ atom_to_list(OsFamily).
+
get_deprecated_3(Get, Config, OldOpt, NewOpt, Default, When) ->
case Get(Config, NewOpt, Default) of
Default ->
@@ -331,6 +336,16 @@ get_deprecated_3(Get, Config, OldOpt, NewOpt, Default, When) ->
New
end.
+get_experimental_3(Get, Config, Opt, Default) ->
+ Val = Get(Config, Opt, Default),
+ case Val of
+ Default ->
+ Default;
+ Val ->
+ ?CONSOLE("NOTICE: Using experimental option '~p'~n", [Opt]),
+ Val
+ end.
+
%% We do the shell variable substitution ourselves on Windows and hope that the
%% command doesn't use any other shell magic.
patch_on_windows(Cmd, Env) ->
@@ -341,7 +356,8 @@ patch_on_windows(Cmd, Env) ->
expand_env_variable(Acc, Key, Value)
end, Cmd, Env),
%% Remove left-over vars
- re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "", [global, {return, list}]);
+ re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "",
+ [global, {return, list}]);
_ ->
Cmd
end.
@@ -427,23 +443,70 @@ emulate_escript_foldl(Fun, Acc, File) ->
Error
end.
-vcs_vsn_cmd(git) ->
- %% git describe the last commit that touched CWD
- %% required for correct versioning of apps in subdirs, such as apps/app1
- case os:type() of
- {win32,nt} ->
- "FOR /F \"usebackq tokens=* delims=\" %i in "
- "(`git log -n 1 \"--pretty=format:%h\" .`) do "
- "@git describe --always --tags %i";
- _ ->
- "git describe --always --tags `git log -n 1 --pretty=format:%h .`"
- end;
-vcs_vsn_cmd(hg) -> "hg identify -i";
-vcs_vsn_cmd(bzr) -> "bzr revno";
-vcs_vsn_cmd(svn) -> "svnversion";
+vcs_vsn_1(Vcs, Dir) ->
+ case vcs_vsn_cmd(Vcs) of
+ {unknown, VsnString} ->
+ ?DEBUG("vcs_vsn: Unknown VCS atom in vsn field: ~p\n", [Vcs]),
+ VsnString;
+ {cmd, CmdString} ->
+ vcs_vsn_invoke(CmdString, Dir);
+ Cmd ->
+ %% If there is a valid VCS directory in the application directory,
+ %% use that version info
+ Extension = lists:concat([".", Vcs]),
+ case filelib:is_dir(filename:join(Dir, Extension)) of
+ true ->
+ ?DEBUG("vcs_vsn: Primary vcs used for ~s\n", [Dir]),
+ vcs_vsn_invoke(Cmd, Dir);
+ false ->
+ %% No VCS directory found for the app. Depending on source
+ %% tree structure, there may be one higher up, but that can
+ %% yield unexpected results when used with deps. So, we
+ %% fallback to searching for a priv/vsn.Vcs file.
+ VsnFile = filename:join([Dir, "priv", "vsn" ++ Extension]),
+ case file:read_file(VsnFile) of
+ {ok, VsnBin} ->
+ ?DEBUG("vcs_vsn: Read ~s from priv/vsn.~p\n",
+ [VsnBin, Vcs]),
+ string:strip(binary_to_list(VsnBin), right, $\n);
+ {error, enoent} ->
+ ?DEBUG("vcs_vsn: Fallback to vcs for ~s\n", [Dir]),
+ vcs_vsn_invoke(Cmd, Dir)
+ end
+ end
+ end.
+
+vcs_vsn_cmd(git) -> "git describe --always --tags";
+vcs_vsn_cmd(hg) -> "hg identify -i";
+vcs_vsn_cmd(bzr) -> "bzr revno";
+vcs_vsn_cmd(svn) -> "svnversion";
+vcs_vsn_cmd(fossil) -> "fossil info";
vcs_vsn_cmd({cmd, _Cmd}=Custom) -> Custom;
vcs_vsn_cmd(Version) -> {unknown, Version}.
vcs_vsn_invoke(Cmd, Dir) ->
{ok, VsnString} = rebar_utils:sh(Cmd, [{cd, Dir}, {use_stdout, false}]),
string:strip(VsnString, right, $\n).
+
+%%
+%% Filter a list of erl_opts platform_define options such that only
+%% those which match the provided architecture regex are returned.
+%%
+filter_defines([], Acc) ->
+ lists:reverse(Acc);
+filter_defines([{platform_define, ArchRegex, Key} | Rest], Acc) ->
+ case rebar_utils:is_arch(ArchRegex) of
+ true ->
+ filter_defines(Rest, [{d, Key} | Acc]);
+ false ->
+ filter_defines(Rest, Acc)
+ end;
+filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) ->
+ case rebar_utils:is_arch(ArchRegex) of
+ true ->
+ filter_defines(Rest, [{d, Key, Value} | Acc]);
+ false ->
+ filter_defines(Rest, Acc)
+ end;
+filter_defines([Opt | Rest], Acc) ->
+ filter_defines(Rest, [Opt | Acc]).
diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl
index 73afdf9..84b59f6 100644
--- a/src/rebar_xref.erl
+++ b/src/rebar_xref.erl
@@ -47,13 +47,13 @@ xref(Config, _) ->
xref:set_default(xref, [{warnings,
rebar_config:get(Config, xref_warnings, false)},
- {verbose, rebar_config:is_verbose()}]),
+ {verbose, rebar_config:is_verbose(Config)}]),
{ok, _} = xref:add_directory(xref, "ebin"),
%% Save the code path prior to doing anything
OrigPath = code:get_path(),
- true = code:add_path(filename:join(rebar_utils:get_cwd(), "ebin")),
+ true = code:add_path(rebar_utils:ebin_dir()),
%% Get list of xref checks we want to run
XrefChecks = rebar_config:get(Config, xref_checks,
@@ -90,7 +90,7 @@ xref(Config, _) ->
case lists:member(false, [ExportsNoWarn, UndefNoWarn, QueryNoWarn]) of
true ->
- ?ABORT;
+ ?FAIL;
false ->
ok
end.
@@ -146,10 +146,10 @@ filter_away_ignored(UnusedExports) ->
%% any functions marked to ignore. We then use this list to mask any
%% functions marked as unused exports by xref
F = fun(Mod) ->
- Attrs = kf(attributes, Mod:module_info()),
- Ignore = kf(ignore_xref, Attrs),
- Callbacks =
- [B:behaviour_info(callbacks) || B <- kf(behaviour, Attrs)],
+ Attrs = Mod:module_info(attributes),
+ Ignore = keyall(ignore_xref, Attrs),
+ Callbacks = [B:behaviour_info(callbacks)
+ || B <- keyall(behaviour, Attrs)],
[{Mod, F, A} || {F, A} <- Ignore ++ lists:flatten(Callbacks)]
end,
AttrIgnore =
@@ -157,14 +157,8 @@ filter_away_ignored(UnusedExports) ->
lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))),
[X || X <- UnusedExports, not lists:member(X, AttrIgnore)].
-
-kf(Key, List) ->
- case lists:keyfind(Key, 1, List) of
- {Key, Value} ->
- Value;
- false ->
- []
- end.
+keyall(Key, List) ->
+ lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
display_mfas([], _Message) ->
ok;
diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl
index 2b9c44e..7b2eec5 100644
--- a/test/rebar_eunit_tests.erl
+++ b/test/rebar_eunit_tests.erl
@@ -59,36 +59,183 @@ eunit_test_() ->
?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
end}.
+eunit_with_suites_and_tests_test_() ->
+ [{"Ensure EUnit runs selected suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests in 'test' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
+
+ {"Selected suite tests in 'src' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2:") =/= 0)},
+
+ {"Unselected suite tests in 'test' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
+
+ {"Unselected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
+
+ {"Selected suite tests are only run once",
+ ?_assert(string:str(RebarOut, "All 4 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs selected _tests suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2_tests")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests in 'test' directory are found and run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2_tests:") =/= 0)},
+
+ {"Selected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod2:") =:= 0)},
+
+ {"Unselected suite tests in 'test' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod_tests:") =:= 0)},
+
+ {"Unselected suite tests in 'src' directory are not run",
+ ?_assert(string:str(RebarOut, "myapp_mymod:") =:= 0)},
+
+ {"Selected suite tests are only run once",
+ ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific test defined in a selected suite",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2 tests=myprivate2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests are found and run",
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0") =/= 0)},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific generator test defined in a selected suite",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod3 tests=mygenerator")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite's generator test is found and run",
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod3:mygenerator_test_/0") =/= 0)},
+
+ {"Selected suite's generator test raises an error",
+ ?_assert(string:str(RebarOut,
+ "assertEqual_failed") =/= 0)},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Failed: 1.") =/= 0)}]
+ end},
+ {"Ensure EUnit runs specific tests defined in selected suites",
+ setup, fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod,myapp_mymod2"
+ " tests=myprivate,myfunc2")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod:myprivate_test/0") =/= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0") =/= 0),
+ ?_assert(
+ string:str(RebarOut,
+ "myapp_mymod2_tests:myfunc2_test/0") =/= 0)]},
+
+ {"Selected suite tests are run once",
+ ?_assert(string:str(RebarOut, "All 3 tests passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs specific test in a _tests suite",
+ setup,
+ fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit suites=myapp_mymod2_tests tests=common_name_test")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Only selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod2:common_name_test/0") =:= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2_tests:common_name_test/0")
+ =/= 0)]},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "Test passed") =/= 0)}]
+ end},
+ {"Ensure EUnit runs a specific test without a specified suite",
+ setup,
+ fun() ->
+ setup_project_with_multiple_modules(),
+ rebar("-v eunit tests=myprivate")
+ end,
+ fun teardown/1,
+ fun(RebarOut) ->
+ [{"Only selected suite tests are found and run",
+ [?_assert(string:str(RebarOut,
+ "myapp_mymod:myprivate_test/0") =/= 0),
+ ?_assert(string:str(RebarOut,
+ "myapp_mymod2:myprivate2_test/0")
+ =/= 0)]},
+
+ {"Selected suite tests is run once",
+ ?_assert(string:str(RebarOut, "All 2 tests passed") =/= 0)}]
+ end}].
+
cover_test_() ->
{"Ensure Cover runs with tests in a test dir and no defined suite",
setup, fun() -> setup_cover_project(), rebar("-v eunit") end,
fun teardown/1,
- [{"All cover reports are generated",
- assert_files_in("the temporary eunit directory",
- expected_cover_generated_files())},
+ fun(RebarOut) ->
+ [{"Error messages are not present",
+ ?_assert(string:str(RebarOut, "Cover analyze failed for") =:= 0)},
+
+ {"All cover reports are generated",
+ assert_files_in("the temporary eunit directory",
+ expected_cover_generated_files())},
- {"Only production modules get coverage reports",
- assert_files_not_in("the temporary eunit directory",
- [".eunit/myapp_mymod_tests.COVER.html"])}]}.
+ {"Only production modules get coverage reports",
+ assert_files_not_in("the temporary eunit directory",
+ [".eunit/myapp_mymod_tests.COVER.html"])}]
+ end}.
cover_with_suite_test_() ->
{"Ensure Cover runs with Tests in a test dir and a test suite",
setup,
fun() ->
setup_cover_project_with_suite(),
- rebar("-v eunit suite=mysuite")
+ rebar("-v eunit suites=mysuite")
end,
fun teardown/1,
- [{"All cover reports are generated",
- assert_files_in("the temporary eunit directory",
- expected_cover_generated_files())},
-
- {"Only production modules get coverage reports",
- assert_files_not_in("the temporary eunit directory",
- [".eunit/myapp_mymod_tests.COVER.html",
- ".eunit/mysuite.COVER.html"])}]}.
+ fun(RebarOut) ->
+ [{"Error messages are not present",
+ ?_assert(string:str(RebarOut, "Cover analyze failed for") =:= 0)},
+
+ {"Cover reports are generated for module",
+ assert_files_in("the temporary eunit directory",
+ [".eunit/index.html",
+ ".eunit/mysuite.COVER.html"])},
+
+ {"Only production modules get coverage reports",
+ assert_files_not_in("the temporary eunit directory",
+ [".eunit/myapp_app.COVER.html",
+ ".eunit/myapp_mymod.COVER.html",
+ ".eunit/myapp_sup.COVER.html",
+ ".eunit/myapp_mymod_tests.COVER.html"])}]
+ end}.
expected_cover_generated_files() ->
[".eunit/index.html",
@@ -155,6 +302,28 @@ basic_setup_test_() ->
"-include_lib(\"eunit/include/eunit.hrl\").\n",
"myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).\n"]).
+-define(myapp_mymod2,
+ ["-module(myapp_mymod2).\n",
+ "-export([myfunc2/0]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc2() -> ok.\n",
+ "myprivate2_test() -> ?assert(true).\n",
+ "common_name_test() -> ?assert(true).\n"]).
+
+-define(myapp_mymod2_tests,
+ ["-module(myapp_mymod2_tests).\n",
+ "-compile([export_all]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc2_test() -> ?assertMatch(ok, myapp_mymod2:myfunc2()).\n",
+ "common_name_test() -> ?assert(true).\n"]).
+
+-define(myapp_mymod3,
+ ["-module(myapp_mymod3).\n",
+ "-export([myfunc3/0]).\n",
+ "-include_lib(\"eunit/include/eunit.hrl\").\n",
+ "myfunc3() -> ok.\n",
+ "mygenerator_test_() -> [?_assertEqual(true, false)].\n"]).
+
-define(mysuite,
["-module(mysuite).\n",
"-export([all_test_/0]).\n",
@@ -183,6 +352,12 @@ setup_basic_project() ->
ok = file:write_file("test/myapp_mymod_tests.erl", ?myapp_mymod_tests),
ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod).
+setup_project_with_multiple_modules() ->
+ setup_basic_project(),
+ ok = file:write_file("test/myapp_mymod2_tests.erl", ?myapp_mymod2_tests),
+ ok = file:write_file("src/myapp_mymod2.erl", ?myapp_mymod2),
+ ok = file:write_file("src/myapp_mymod3.erl", ?myapp_mymod3).
+
setup_cover_project() ->
setup_basic_project(),
ok = file:write_file("rebar.config", "{cover_enabled, true}.\n").
@@ -247,6 +422,5 @@ assert_full_coverage(Mod) ->
Result = [X || X <- string:tokens(binary_to_list(F), "\n"),
string:str(X, Mod) =/= 0,
string:str(X, "100%") =/= 0],
- ok = file:close(F),
?assert(length(Result) =:= 1)
end.