diff options
66 files changed, 2487 insertions, 1506 deletions
@@ -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" @@ -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 @@ -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) @@ -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 @@ -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. |