diff -Nru rebar-2.0.0/bootstrap rebar-2.6.0/bootstrap --- rebar-2.0.0/bootstrap 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/bootstrap 2015-06-19 16:14:28.000000000 +0000 @@ -3,19 +3,28 @@ %% ex: ft=erlang ts=4 sw=4 et main(Args) -> + case lists:member("--help", Args) of + true -> + usage(), + halt(0); + false -> + ok + end, + %% Get a string repr of build time 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"}]), + 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 @@ -27,10 +36,23 @@ %% Extract the system info of the version of OTP we use to compile rebar OtpInfo = string:strip(erlang:system_info(otp_release), both, $\n), + %% Types dict:dict() and digraph:digraph() have been introduced in + %% Erlang 17. + %% At the same time, their counterparts dict() and digraph() are to be + %% deprecated in Erlang 18. namespaced_types option is used to select + %% proper type name depending on the OTP version used. + NamespacedTypes = case is_otp(OtpInfo, "^[0-9]+") of + true -> {d, namespaced_types}; + false -> undefined + end, + %% Compile all src/*.erl to ebin - case make:files(filelib:wildcard("src/*.erl"), + %% To not accidentally try to compile files like Mac OS X resource forks, + %% we only look for rebar source files that start with a letter. + case make:files(filelib:wildcard("src/[a-zA-Z]*.erl"), [{outdir, "ebin"}, {i, "include"}, DebugFlag, + NamespacedTypes, {d, 'BUILD_TIME', Built}, {d, 'VCS_INFO', VcsInfo}, {d, 'OTP_INFO', OtpInfo}]) of @@ -53,35 +75,10 @@ %% Add ebin/ to our path true = code:add_path("ebin"), - %% Run rebar to do proper .app validation and such - rebar:main(["compile"] ++ Args), - - %% Read the contents of the files in ebin and templates; note that we - %% place all the beam files at the top level of the code archive so - %% that code loading works properly. - Files = load_files("*", "ebin") ++ load_files("priv/templates/*", "."), - - case zip:create("mem", Files, [memory]) of - {ok, {"mem", ZipBin}} -> - %% Archive was successfully created. Prefix that binary with our - %% header and write to "rebar" file. - %% Without -noshell -noinput escript consumes all input that would - %% otherwise go to the shell for the next command. - Script = <<"#!/usr/bin/env escript\n%%! -noshell -noinput\n", - ZipBin/binary>>, - case file:write_file("rebar", Script) of - ok -> - ok; - {error, WriteError} -> - io:format("Failed to write rebar script: ~p\n", - [WriteError]), - halt(1) - end; - {error, ZipError} -> - io:format("Failed to construct rebar script archive: ~p\n", - [ZipError]), - halt(1) - end, + %% Run rebar compile to do proper .app validation etc. + %% and rebar escriptize to create the rebar script + RebarArgs = Args -- ["debug"], %% Avoid trying to run 'debug' command + rebar:main(["compile", "escriptize"] ++ RebarArgs), %% Finally, update executable perms for our script on *nix, %% or write out script files on win32. @@ -103,6 +100,17 @@ "Place this script anywhere in your path\n" "and you can use rebar to build OTP-compliant apps.\n"). +usage() -> + io:format("Usage: bootstrap [OPTION]...~n"), + io:format(" force=1 unconditional build~n"), + io:format(" debug add debug information~n"). + +is_otp(OtpInfo, Regex) -> + case re:run(OtpInfo, Regex, [{capture, none}]) of + match -> true; + nomatch -> false + end. + rm(Path) -> NativePath = filename:nativename(Path), Cmd = case os:type() of @@ -113,24 +121,36 @@ ok. build_time() -> - {{Y, M, D}, {H, Min, S}} = calendar:now_to_universal_time(now()), + {{Y, M, D}, {H, Min, S}} = calendar:now_to_universal_time(rebar_now()), lists:flatten(io_lib:format("~4..0w~2..0w~2..0w_~2..0w~2..0w~2..0w", [Y, M, D, H, Min, S])). - - -load_files(Wildcard, Dir) -> - [read_file(Filename, Dir) || Filename <- filelib:wildcard(Wildcard, Dir)]. - -read_file(Filename, Dir) -> - {ok, Bin} = file:read_file(filename:join(Dir, Filename)), - {Filename, Bin}. +rebar_now() -> + case erlang:function_exported(erlang, timestamp, 0) of + true -> + erlang:timestamp(); + false -> + %% erlang:now/0 was deprecated in 18.0, and as the escript has to + %% pass erl_lint:module/1 (even without -mode(compile)), we would + %% see a deprecation warning for erlang:now/0. One solution is to + %% use -compile({nowarn_deprecated_function, [{erlang, now, 0}]}), + %% but that would raise a warning in versions older than 18.0. + %% Calling erlang:now/0 via apply/3 avoids that. + apply(erlang, now, []) + end. 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 -Nru rebar-2.0.0/CONTRIBUTING.md rebar-2.6.0/CONTRIBUTING.md --- rebar-2.0.0/CONTRIBUTING.md 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/CONTRIBUTING.md 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,121 @@ +Contributing to rebar +--------------------- + +Before implementing a new feature, please submit a ticket to discuss your plans. +The feature might have been rejected already, or the implementation might already be decided. + +See [Community and Resources](README.md#community-and-resources). + +Code style +---------- + +The following rules must be followed: + * Do not introduce trailing whitespace + * Do not mix spaces and tabs + * Do not introduce lines longer than 80 characters + +The following rules should be followed: + * Write small functions whenever possible + * Avoid having too many clauses containing clauses containing clauses. + Basically, avoid deeply nested functions. + +[erlang-mode (emacs)](http://www.erlang.org/doc/man/erlang.el.html) +indentation is preferred. This will keep the code base consistent. +vi users are encouraged to give [Vim emulation](http://emacswiki.org/emacs/Evil) ([more +info](https://gitorious.org/evil/pages/Home)) a try. + +Pull requests and branching +--------------------------- + +Use one topic branch per pull request. If you do that, you can add extra commits or fix up +buggy commits via `git rebase -i`, and update the branch. The updated branch will be +visible in the same pull request. Therefore, you should not open a new pull request when +you have to fix your changes. + +Do not commit to master in your fork. + +Provide a clean branch without merge commits. + +Tests +----- + +As a general rule, any behavioral change to rebar requires a test to go with it. If there's +already a test case, you may have to modify that one. If there isn't a test case or a test +suite, add a new test case or suite in `inttest/`. [retest](https://github.com/dizzyd/retest) based tests are preferred, but +we also have EUnit tests in `test/`. + +Say you've added a new test case in `inttest/erlc`. To only execute the modified suite, +you would do the following: +```sh +# First we build rebar and its deps to also get `deps/retest/retest` +$ make debug deps +# Now we can test the modified erlc suite +$ deps/retest/retest -v inttest/erlc +``` + +To test EUnit tests, you would do: +```sh +$ make debug +$ ./rebar -v eunit +``` + +You can also run `make test` to execute both EUnit and [retest](https://github.com/dizzyd/retest) tests as `make check` does. + +Credit +------ + +To give everyone proper credit in addition to the git history, please feel free to append +your name to `THANKS` in your first contribution. + +Committing your changes +----------------------- + +Please ensure that all commits pass all tests, and do not have extra Dialyzer warnings. +To do that, run `make check`. If you didn't build via `make debug` at first, the beam files in +`ebin/` might be missing debug_info required for [xref](http://www.erlang.org/doc/man/xref.html) +and [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html), causing a test +failure. +If that happens, running `make clean` before running `make check` could solve the problem. + +#### Structuring your commits + +Fixing a bug is one commit. +Adding a feature is one commit. +Adding two features is two commits. +Two unrelated changes is two commits. + +If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup commit into +the original commit. + +#### Writing Commit Messages + +It's important to write a proper commit title and description. The commit title must be +at most 50 characters; it is the first line of the commit text. The second line of the +commit text must be left blank. The third line and beyond is the commit message. You +should write a commit message. If you do, wrap all lines at 72 characters. You should +explain what the commit does, what references you used, and any other information +that helps understanding your changes. + +Basically, structure your commit message like this: + +
+One line summary (at most 50 characters)
+
+Longer description (wrap at 72 characters)
+
+ +##### Commit title/summary + +* At most 50 characters +* What was changed +* Imperative present tense (Fix, Add, Change) + * `Fix bug 123` + * `Add 'foobar' command` + * `Change default timeout to 123` +* No period + +##### Commit description + +* Wrap at 72 characters +* Why, explain intention and implementation approach +* Present tense diff -Nru rebar-2.0.0/debian/changelog rebar-2.6.0/debian/changelog --- rebar-2.0.0/debian/changelog 2013-10-15 02:14:00.000000000 +0000 +++ rebar-2.6.0/debian/changelog 2016-03-12 09:03:18.000000000 +0000 @@ -1,18 +1,55 @@ -rebar (2.0.0-5) unstable; urgency=low +rebar (2.6.0-2~14.04.1) trusty; urgency=medium + * backport to trusty + + -- Richard Hansen Sat, 12 Mar 2016 02:28:30 -0500 + +rebar (2.6.0-2) unstable; urgency=medium + + * Fix build on arm64 with commad line option -m64. (Closes: #795932) + Add patches/fix-build-on-64bit-arch.patch. + * Update contorol file. + - Add erlang-syntax-tools to Depends. (Closes: #794223) + - Add Vcs-Git and Vcs-Browser field. + + -- Nobuhiro Iwamatsu Thu, 03 Sep 2015 03:03:33 +0900 + +rebar (2.6.0-1) unstable; urgency=medium + + * New upstream released. + * Update debian/rules. (Closes: #790633) + - Disabled test. Test of rebar became necessary retest package. + However, this package is not yet. This is until the retest is installed on + Debian, to disable test. + + -- Nobuhiro Iwamatsu Sat, 04 Jul 2015 03:25:04 +0900 + +rebar (2.5.1-2) unstable; urgency=medium + + * Upload to unstable. + + -- Nobuhiro Iwamatsu Thu, 30 Apr 2015 06:38:58 +0900 + +rebar (2.5.1-1) experimental; urgency=medium + + * New upstream released. * Update debian/control. - Add erlang-dialyzer to Depends. + - Bump Standards-Version to 3.9.6. + - Update Homepage section to https://github.com/rebar/rebar. + - Add erlang-syntax-tools and erlang-eunit to Build-Depends. + * Update debian/copyright. + - Update Source section to https://github.com/rebar/rebar.git. + * Update debian/watch. + - Update URL to https://github.com/rebar/rebar. - -- Nobuhiro Iwamatsu Tue, 15 Oct 2013 11:13:27 +0900 + -- Nobuhiro Iwamatsu Wed, 14 Jan 2015 08:26:22 +0900 rebar (2.0.0-4) unstable; urgency=low * Update debian/control. - - Add erlang-dialyzer to Build-Depends. - Fix FTBFS if built with Erlang R16B01. - - Bump Standards-Version to 3.9.4. + - Dump Standards-Version to 3.9.4. - -- Nobuhiro Iwamatsu Mon, 29 Jul 2013 09:59:58 +0900 + -- Nobuhiro Iwamatsu Sun, 19 May 2013 03:55:27 +0900 rebar (2.0.0-3) unstable; urgency=low diff -Nru rebar-2.0.0/debian/control rebar-2.6.0/debian/control --- rebar-2.0.0/debian/control 2013-10-15 02:13:20.000000000 +0000 +++ rebar-2.6.0/debian/control 2015-09-02 18:16:42.000000000 +0000 @@ -3,9 +3,12 @@ Uploaders: Nobuhiro Iwamatsu Section: devel Priority: optional -Standards-Version: 3.9.4 -Homepage: https://github.com/basho/rebar -Build-Depends: debhelper (>= 9), erlang-dev, erlang-tools, erlang-dialyzer +Build-Depends: debhelper (>= 9), erlang-dev, erlang-tools, erlang-syntax-tools, + erlang-eunit +Standards-Version: 3.9.6 +Homepage: https://github.com/rebar/rebar +Vcs-Git: git://anonscm.debian.org/pkg-leofs/rebar.git +Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-leofs/rebar.git;a=summary Package: rebar Architecture: any @@ -13,7 +16,7 @@ Priority: optional Depends: ${misc:Depends}, ${shlibs:Depends}, erlang-base | erlang-base-hipe, - erlang-dev, erlang-tools, erlang-dialyzer + erlang-dev, erlang-tools, erlang-syntax-tools Recommends: git, mercurial Enhances: erlang Description: Sophisticated build-tool for Erlang projects that follows OTP principles diff -Nru rebar-2.0.0/debian/copyright rebar-2.6.0/debian/copyright --- rebar-2.0.0/debian/copyright 2013-07-29 00:50:04.000000000 +0000 +++ rebar-2.6.0/debian/copyright 2015-01-14 00:19:33.000000000 +0000 @@ -1,7 +1,7 @@ Format: Format: http://dep.debian.net/deps/dep5 Name: rebar Maintainer: Nobuhiro Iwamatsu -Source: https://github.com/basho/rebar +Source: https://github.com/rebar/rebar.git Files: src/getopt.erl Copyright: Juan Jose Comellas diff -Nru rebar-2.0.0/debian/patches/Add-build-target-to-Makefile.patch rebar-2.6.0/debian/patches/Add-build-target-to-Makefile.patch --- rebar-2.0.0/debian/patches/Add-build-target-to-Makefile.patch 2013-07-29 00:50:04.000000000 +0000 +++ rebar-2.6.0/debian/patches/Add-build-target-to-Makefile.patch 2015-07-13 19:11:28.000000000 +0000 @@ -1,49 +1,35 @@ -From e815298b6abff3ac1cf58c360e03ee9a849a9614 Mon Sep 17 00:00:00 2001 +From 398c3174cf5d5709816093c51e94e7afd3c9c103 Mon Sep 17 00:00:00 2001 From: Nobuhiro Iwamatsu -Date: Mon, 16 Jul 2012 01:56:58 +0900 +Date: Wed, 14 Jan 2015 08:38:20 +0900 Subject: [PATCH] Add build target to Makefile Signed-off-by: Nobuhiro Iwamatsu --- - Makefile | 15 +++++++++++---- - 1 file changed, 11 insertions(+), 4 deletions(-) + Makefile | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile -index 1fda878..b11dccf 100644 +index d0d16f8..db23420 100644 --- a/Makefile +++ b/Makefile -@@ -1,6 +1,10 @@ --.PHONY: dialyzer_warnings xref_warnings -+.PHONY: all build clean install +@@ -1,7 +1,7 @@ +-.PHONY: clean xref_warnings deps test test_eunit test_inttest ++.PHONY: clean xref_warnings deps test test_eunit test_inttest all build install --all: -+all: build -+ -+build: bootstrap -+ -+bootstrap: - ./bootstrap - - clean: -@@ -9,7 +13,7 @@ clean: - debug: - @./bootstrap debug +-REBAR=$(PWD)/rebar +-RETEST=$(PWD)/deps/retest/retest ++REBAR=$(CURDIR)/rebar ++RETEST=$(CURDIR)/deps/retest/retest + LOG_LEVEL?=debug + RT_TARGETS?=inttest --check: debug xref dialyzer -+check: debug # xref dialyzer +@@ -33,6 +33,9 @@ binary: clean all + @cp rebar ../rebar.wiki/rebar + (cd ../rebar.wiki && git commit -m "Update $(VSN)" rebar) - xref: - @./rebar xref -@@ -24,4 +28,7 @@ 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 -+ (cd ../rebar.wiki && git commit -m "Update $(VSN)" rebar) -+ -+install: build ++install: all + install -D -m 755 ./rebar $(DESTDIR)/usr/bin/rebar --- -1.7.10.4 - ++ + deps: + @REBAR_EXTRA_DEPS=1 $(REBAR) get-deps + @(cd deps/retest && $(REBAR) compile escriptize) diff -Nru rebar-2.0.0/debian/patches/fix-build-on-64bit-arch.patch rebar-2.6.0/debian/patches/fix-build-on-64bit-arch.patch --- rebar-2.0.0/debian/patches/fix-build-on-64bit-arch.patch 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/debian/patches/fix-build-on-64bit-arch.patch 2015-09-02 18:09:23.000000000 +0000 @@ -0,0 +1,23 @@ +Description: Fix build on arm64 with commad line option -m64 +Author: Christopher S. Meiklejohn +Bug: https://github.com/rebar/rebar/pull/459 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=795932 +Last-Update: 2015-09-03 + --- + +diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl +index 5855589..037567a 100644 +--- a/src/rebar_port_compiler.erl ++++ b/src/rebar_port_compiler.erl +@@ -604,11 +604,6 @@ default_env() -> + {"solaris.*-64$", "CXXFLAGS", "-D_REENTRANT -m64 $CXXFLAGS"}, + {"solaris.*-64$", "LDFLAGS", "-m64 $LDFLAGS"}, + +- %% Linux specific flags for multiarch +- {"linux.*-64$", "CFLAGS", "-m64 $CFLAGS"}, +- {"linux.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, +- {"linux.*-64$", "LDFLAGS", "$LDFLAGS"}, +- + %% OS X Leopard flags for 64-bit + {"darwin9.*-64$", "CFLAGS", "-m64 $CFLAGS"}, + {"darwin9.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, diff -Nru rebar-2.0.0/debian/patches/series rebar-2.6.0/debian/patches/series --- rebar-2.0.0/debian/patches/series 2013-07-29 00:50:04.000000000 +0000 +++ rebar-2.6.0/debian/patches/series 2015-09-02 18:03:04.000000000 +0000 @@ -1 +1,2 @@ Add-build-target-to-Makefile.patch +fix-build-on-64bit-arch.patch diff -Nru rebar-2.0.0/debian/rules rebar-2.6.0/debian/rules --- rebar-2.0.0/debian/rules 2013-07-29 00:50:04.000000000 +0000 +++ rebar-2.6.0/debian/rules 2015-07-13 19:13:21.000000000 +0000 @@ -2,3 +2,5 @@ %: dh $@ + +override_dh_auto_test: diff -Nru rebar-2.0.0/debian/watch rebar-2.6.0/debian/watch --- rebar-2.0.0/debian/watch 2013-07-29 00:50:04.000000000 +0000 +++ rebar-2.6.0/debian/watch 2015-01-14 00:20:21.000000000 +0000 @@ -1,4 +1,4 @@ version=3 opts=uversionmangle=s/_/./g;s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha|b|a)[\-\.]?\d*)$/$1~$2/ \ - https://github.com/basho/rebar/tags .*[^n]/(?:v||release-|rebar[_\-])(\d.*)\.(?:tgz|tbz2|txz|tar\.(?:gz|bz2|xz)) + https://https://github.com/rebar/rebar/tags .*[^n]/(?:v||release-|rebar[_\-])(\d.*)\.(?:tgz|tbz2|txz|tar\.(?:gz|bz2|xz)) # Bart Martens Tue, 15 Jan 2013 20:52:43 +0000 diff -Nru rebar-2.0.0/dialyzer_reference rebar-2.6.0/dialyzer_reference --- rebar-2.0.0/dialyzer_reference 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/dialyzer_reference 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ - -rebar_utils.erl:154: Call to missing or unexported function escript:foldl/3 diff -Nru rebar-2.0.0/ebin/rebar.app rebar-2.6.0/ebin/rebar.app --- rebar-2.0.0/ebin/rebar.app 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/ebin/rebar.app 2015-06-19 16:14:28.000000000 +0000 @@ -3,50 +3,70 @@ {application, rebar, [{description, "Rebar: Erlang Build Tool"}, - {vsn, "2.0.0"}, + {vsn, "2.6.0"}, {modules, [ rebar, rebar_abnfc_compiler, - rebar_appups, rebar_app_utils, + rebar_appups, + rebar_asn1_compiler, + rebar_dia_compiler, rebar_base_compiler, + rebar_cleaner, rebar_config, rebar_core, - rebar_cleaner, + rebar_cover_utils, rebar_ct, rebar_deps, - rebar_asn1_compiler, + rebar_dialyzer, rebar_edoc, rebar_erlc_compiler, + rebar_erlydtl_compiler, rebar_escripter, rebar_eunit, rebar_file_utils, rebar_lfe_compiler, - rebar_erlydtl_compiler, rebar_log, + rebar_neotoma_compiler, rebar_otp_app, + rebar_otp_appup, rebar_port_compiler, + rebar_proto_compiler, rebar_protobuffs_compiler, - rebar_neotoma_compiler, + rebar_proto_gpb_compiler, + rebar_qc, rebar_rel_utils, rebar_reltool, rebar_require_vsn, + rebar_shell, rebar_subdirs, rebar_templater, rebar_upgrade, rebar_utils, rebar_xref, - getopt, - mustache ]}, + rebar_metacmds, + rebar_getopt, + rebar_mustache, + rmemo ]}, {registered, []}, - {applications, [kernel, - stdlib, - sasl]}, + {applications, + [ + kernel, + stdlib, + sasl, + compiler, + crypto, + syntax_tools, + tools, + eunit, + reltool, + dialyzer, + asn1, + snmp, + edoc + ]}, {env, [ %% Default log level - {log_level, error}, - - %% Default parallel jobs - {jobs, 3}, + {log_level, warn}, %% any_dir processing modules {any_dir_modules, [ @@ -61,19 +81,27 @@ {modules, [ {app_dir, [ rebar_abnfc_compiler, + rebar_proto_compiler, rebar_protobuffs_compiler, + rebar_proto_gpb_compiler, rebar_neotoma_compiler, rebar_asn1_compiler, + rebar_dia_compiler, + rebar_dialyzer, rebar_erlc_compiler, rebar_lfe_compiler, rebar_erlydtl_compiler, rebar_port_compiler, rebar_otp_app, + rebar_otp_appup, rebar_ct, rebar_eunit, + rebar_qc, rebar_escripter, rebar_edoc, - rebar_xref + rebar_shell, + rebar_xref, + rebar_metacmds ]}, {rel_dir, [ @@ -81,6 +109,14 @@ rebar_reltool, rebar_upgrade ]} - ]} + ]}, + {recursive_cmds, [ + 'check-deps', + compile, + 'delete-deps', + 'get-deps', + 'list-deps', + 'update-deps' + ]} ]} ]}. diff -Nru rebar-2.0.0/.gitignore rebar-2.6.0/.gitignore --- rebar-2.0.0/.gitignore 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/.gitignore 2015-06-19 16:14:28.000000000 +0000 @@ -1,12 +1,11 @@ -*.beam -rebar +/ebin/*.beam +/rebar *~ *.orig .*.swp -rt.work -.hgignore -.eunit -dialyzer_warnings -xref_warnings -rebar.cmd -rebar.ps1 +/rt.work +/rebar.cmd +/.eunit +/deps +/.rebar +env diff -Nru rebar-2.0.0/.hgignore rebar-2.6.0/.hgignore --- rebar-2.0.0/.hgignore 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/.hgignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -\.beam -^rebar$ -.~ -\.orig -\.swp -rt.work/* -^.gitignore$ diff -Nru rebar-2.0.0/.hgtags rebar-2.6.0/.hgtags --- rebar-2.0.0/.hgtags 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/.hgtags 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -e8747041ef63f1b394d3b156c72c5bc12e92ecc4 RELEASE-1 diff -Nru rebar-2.0.0/include/rebar.hrl rebar-2.6.0/include/rebar.hrl --- rebar-2.0.0/include/rebar.hrl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/include/rebar.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -1,5 +1,7 @@ --define(FAIL, throw({error, failed})). - +%% 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)). @@ -7,6 +9,6 @@ -define(DEBUG(Str, Args), rebar_log:log(debug, Str, Args)). -define(INFO(Str, Args), rebar_log:log(info, Str, Args)). -define(WARN(Str, Args), rebar_log:log(warn, Str, Args)). --define(ERROR(Str, Args), rebar_log:log(error, Str, Args)). +-define(ERROR(Str, Args), rebar_log:log(standard_error, error, Str, Args)). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). diff -Nru rebar-2.0.0/inttest/app_src/app_src.erl rebar-2.6.0/inttest/app_src/app_src.erl --- rebar-2.0.0/inttest/app_src/app_src.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src/app_src.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(app_src). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/app_src/app_src_rt.erl rebar-2.6.0/inttest/app_src/app_src_rt.erl --- rebar-2.0.0/inttest/app_src/app_src_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src/app_src_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,78 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Vlad Dumitrescu +%% +%% 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(app_src_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {create, "src/app_src.app.src", app(app_src)}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + LineRegexp = "DEBUG: Consult config file .*/app_src\.app\.src.*", + ?assertEqual(true, has_line(Output, LineRegexp)), + retest_log:log(debug, "Evaluated .app.src.script~n", []), + + %% check that ebin/app_src.app exists + ?assertMatch(true, filelib:is_regular("ebin/app_src.app")), + retest_log:log(debug, "Generated ebin/.app~n", []), + + %% check that ebin/.app has vsn="2" + {ok, Bin} = file:read_file("ebin/app_src.app"), + Str = binary_to_list(Bin), + ?assertMatch({match, _}, re:run(Str, "{vsn, *\"2\"}")), + retest_log:log(debug, "Variable replacement in .app is ok.~n", []), + + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. + +%% +%% Generate the contents of a simple .app.src file +%% +app(Name) -> + "{application, " ++ atom_to_list(Name) ++ ", + [{vsn, \"2\"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}.\n". diff -Nru rebar-2.0.0/inttest/app_src_script/app_src_script.erl rebar-2.6.0/inttest/app_src_script/app_src_script.erl --- rebar-2.0.0/inttest/app_src_script/app_src_script.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src_script/app_src_script.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(app_src_script). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/app_src_script/app_src_script_rt.erl rebar-2.6.0/inttest/app_src_script/app_src_script_rt.erl --- rebar-2.0.0/inttest/app_src_script/app_src_script_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src_script/app_src_script_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,83 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Vlad Dumitrescu +%% +%% 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(app_src_script_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {create, "src/app_src_script.app.src.script", app_script(app_src_script)}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + Regexp = "DEBUG: Evaluating config script .*/app_src_script\.app\.src\.script.*", + ?assertEqual(true, has_line(Output, Regexp)), + retest_log:log(debug, "Evaluated .app.src.script~n", []), + + %% check that ebin/app_src.app exists + ?assertMatch(true, filelib:is_regular("ebin/app_src_script.app")), + retest_log:log(debug, "Generated ebin/.app~n", []), + + %% check that ebin/.app has vsn="2" + {ok, Bin} = file:read_file("ebin/app_src_script.app"), + Str = binary_to_list(Bin), + ?assertMatch({match, _}, re:run(Str, "{vsn, *\"2\"}")), + retest_log:log(debug, "Variable replacement in .app is ok.~n", []), + + %% check that ebin/.app doesn't have foo=ok + ?assertMatch(nomatch, re:run(Str, "{foo, *ok}")), + retest_log:log(debug, "app.src hasn't 'foo' config.~n", []), + + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. + +%% +%% Generate the contents of a simple .app.src.script file +%% +app_script(Name) -> + "Vsn=\"2\".\n" ++ + "{application, " ++ atom_to_list(Name) ++ ", + [{vsn, Vsn}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}.\n". diff -Nru rebar-2.0.0/inttest/app_src_script_2/app_src_script_2.erl rebar-2.6.0/inttest/app_src_script_2/app_src_script_2.erl --- rebar-2.0.0/inttest/app_src_script_2/app_src_script_2.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src_script_2/app_src_script_2.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(app_src_script_2). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/app_src_script_2/app_src_script_2_rt.erl rebar-2.6.0/inttest/app_src_script_2/app_src_script_2_rt.erl --- rebar-2.0.0/inttest/app_src_script_2/app_src_script_2_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/app_src_script_2/app_src_script_2_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,93 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Vlad Dumitrescu +%% +%% 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(app_src_script_2_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {create, "src/app_src_script_2.app.src.script", app_script(app_src_script_2)}, + {create, "src/app_src_script_2.app.src", app(app_src_script_2)}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + Regexp = "DEBUG: Evaluating config script .*/app_src_script_2\.app\.src\.script.*", + ?assertEqual(true, has_line(Output, Regexp)), + retest_log:log(debug, "Evaluated .app.src.script~n", []), + + %% check that ebin/app_src.app exists + ?assertMatch(true, filelib:is_regular("ebin/app_src_script_2.app")), + retest_log:log(debug, "Generated ebin/.app~n", []), + + %% check that ebin/.app has vsn="2" (as in .script) + {ok, Bin} = file:read_file("ebin/app_src_script_2.app"), + Str = binary_to_list(Bin), + retest_log:log(debug, "app=~p~n", [Str]), + ?assertMatch({match, _}, re:run(Str, "{vsn, *\"2\"}")), + retest_log:log(debug, "app.src has version from script.~n", []), + + %% check that ebin/.app has foo=ok (from .src) + ?assertMatch({match, _}, re:run(Str, "{foo, *ok}")), + retest_log:log(debug, "app.src has 'foo' config from .src.~n", []), + + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. + +%% +%% Generate the contents of a simple .app.src.script file +%% +app_script(Name) -> + "[{application," ++ atom_to_list(Name) ++ ",Cfg}] = CONFIG, + [{application," ++ atom_to_list(Name) ++ ",lists:keyreplace(vsn, 1, Cfg, + {vsn, \"2\"})}].". + +%% +%% Generate the contents of a simple .app.src file +%% +app(Name) -> + "{application, " ++ atom_to_list(Name) ++ ", + [{vsn, \"3\"}, + {foo, ok}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}.\n". diff -Nru rebar-2.0.0/inttest/appup_src/appup_src_rt.erl rebar-2.6.0/inttest/appup_src/appup_src_rt.erl --- rebar-2.0.0/inttest/appup_src/appup_src_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src/appup_src_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,67 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Luis Rascao (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(appup_src_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {copy, "src", "src"}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + LineRegexp = "Compiled src/app\.appup\.src", + ?assertEqual(true, has_line(Output, LineRegexp)), + + %% check that ebin/app.appup exists + ?assertMatch(true, filelib:is_regular("ebin/app.appup")), + retest_log:log(debug, "Generated ebin/app.appup~n", []), + + %% check that ebin/app.appup has expected version + {ok, [{AppVersion, [{UpgradeFrom, _} | _UpgradeFromRest], [{DowngradeTo, _} | _DowngradeToRest]}]} = + file:consult("ebin/app.appup"), + ?assertEqual(AppVersion, "%VSN%"), + ?assertEqual(UpgradeFrom, "1.3"), + ?assertEqual(DowngradeTo, "1.3"), + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. diff -Nru rebar-2.0.0/inttest/appup_src/src/app.app.src rebar-2.6.0/inttest/appup_src/src/app.app.src --- rebar-2.0.0/inttest/appup_src/src/app.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src/src/app.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +{application, app, + [{vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}. diff -Nru rebar-2.0.0/inttest/appup_src/src/app.appup.src rebar-2.6.0/inttest/appup_src/src/app.appup.src --- rebar-2.0.0/inttest/appup_src/src/app.appup.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src/src/app.appup.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,162 @@ +{"%VSN%", + [ + {"1.3", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_per_bin}, + {add_module, asn1rt_check}, + {add_module, asn1rt_per_bin_rt2ct}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + }, + {"1.3.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_per_bin}, + {add_module, asn1rt_check}, + {add_module, asn1rt_per_bin_rt2ct}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + }, + {"1.3.1.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_per_bin}, + {add_module, asn1rt_check}, + {add_module, asn1rt_per_bin_rt2ct}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + }, + {"1.3.2", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {add_module, asn1rt_per_bin_rt2ct}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + }, + {"1.3.3", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin_rt2ct, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + }, + {"1.3.3.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin_rt2ct, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_bin_v2}, + {add_module, asn1rt_driver_handler}, + {remove, {asn1rt_ber_v1, soft_purge, soft_purge}} + ] + } + ], + [ + {"1.3", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_per_bin, soft_purge, soft_purge}}, + {remove, {asn1rt_check, soft_purge, soft_purge}}, + {remove, {asn1rt_per_bin_rt2ct, soft_purge, soft_purge}}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + }, + {"1.3.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_per_bin, soft_purge, soft_purge}}, + {remove, {asn1rt_check, soft_purge, soft_purge}}, + {remove, {asn1rt_per_bin_rt2ct, soft_purge, soft_purge}}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + }, + {"1.3.1.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_per_bin, soft_purge, soft_purge}}, + {remove, {asn1rt_check, soft_purge, soft_purge}}, + {remove, {asn1rt_per_bin_rt2ct, soft_purge, soft_purge}}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + }, + {"1.3.2", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_per_bin_rt2ct, soft_purge, soft_purge}}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + }, + {"1.3.3", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin_rt2ct, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + }, + {"1.3.3.1", + [ + {load_module, asn1rt_per_v1, soft_purge, soft_purge, []}, + {load_module, asn1rt_ber_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin, soft_purge, soft_purge, []}, + {load_module, asn1rt_check, soft_purge, soft_purge, []}, + {load_module, asn1rt_per_bin_rt2ct, soft_purge, soft_purge, []}, + {add_module, asn1rt_ber_v1}, + {remove, {asn1rt_ber_bin_v2, soft_purge, soft_purge}}, + {remove, {asn1rt_driver_handler, soft_purge, soft_purge}} + ] + } + + ]}. diff -Nru rebar-2.0.0/inttest/appup_src/src/appup_src.erl rebar-2.6.0/inttest/appup_src/src/appup_src.erl --- rebar-2.0.0/inttest/appup_src/src/appup_src.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src/src/appup_src.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(appup_src). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/appup_src_2/appup_src_2_rt.erl rebar-2.6.0/inttest/appup_src_2/appup_src_2_rt.erl --- rebar-2.0.0/inttest/appup_src_2/appup_src_2_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_2/appup_src_2_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,65 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Luis Rascao (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(appup_src_2_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {copy, "src", "src"}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + LineRegexp = "Compiled src/app\.appup\.src", + ?assertEqual(true, has_line(Output, LineRegexp)), + + %% check that ebin/app.appup exists + ?assertMatch(true, filelib:is_regular("ebin/app.appup")), + retest_log:log(debug, "Generated ebin/app.appup~n", []), + + %% check that ebin/app.appup has expected version + {ok, [{AppVersion, [], []}]} = + file:consult("ebin/app.appup"), + ?assertEqual(AppVersion, "1.24"), + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. diff -Nru rebar-2.0.0/inttest/appup_src_2/appup_src_rt_2.erl rebar-2.6.0/inttest/appup_src_2/appup_src_rt_2.erl --- rebar-2.0.0/inttest/appup_src_2/appup_src_rt_2.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_2/appup_src_rt_2.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,67 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Luis Rascao (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(appup_src_rt_2). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {copy, "src", "src"}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + LineRegexp = "Compiled src/app\.appup\.src", + ?assertEqual(true, has_line(Output, LineRegexp)), + + %% check that ebin/app.appup exists + ?assertMatch(true, filelib:is_regular("ebin/app.appup")), + retest_log:log(debug, "Generated ebin/app.appup~n", []), + + %% check that ebin/app.appup has expected version + {ok, [{AppVersion, [{UpgradeFrom, _}], [{DowngradeTo, _}]}]} = + file:consult("ebin/app.appup"), + ?assertEqual(AppVersion, "1.2.2"), + ?assertEqual(UpgradeFrom, "1.2.1"), + ?assertEqual(DowngradeTo, "1.2.1"), + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. diff -Nru rebar-2.0.0/inttest/appup_src_2/src/app.app.src rebar-2.6.0/inttest/appup_src_2/src/app.app.src --- rebar-2.0.0/inttest/appup_src_2/src/app.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_2/src/app.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +{application, app, + [{vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}. diff -Nru rebar-2.0.0/inttest/appup_src_2/src/app.appup.src rebar-2.6.0/inttest/appup_src_2/src/app.appup.src --- rebar-2.0.0/inttest/appup_src_2/src/app.appup.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_2/src/app.appup.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +{"1.24", + [], + [] +}. diff -Nru rebar-2.0.0/inttest/appup_src_2/src/appup_src.erl rebar-2.6.0/inttest/appup_src_2/src/appup_src.erl --- rebar-2.0.0/inttest/appup_src_2/src/appup_src.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_2/src/appup_src.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(appup_src). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/appup_src_script/appup_src_script_rt.erl rebar-2.6.0/inttest/appup_src_script/appup_src_script_rt.erl --- rebar-2.0.0/inttest/appup_src_script/appup_src_script_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_script/appup_src_script_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,67 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Luis Rascao (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(appup_src_script_rt). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {copy, "src", "src"}]. + +run(Dir) -> + retest_log:log(debug, "Running in Dir: ~s~n", [Dir]), + {ok, [_Pid|Output]} = retest:sh("./rebar compile -vv", + [{async, false}]), + + LineRegexp = "Compiled src/app\.appup\.src", + ?assertEqual(true, has_line(Output, LineRegexp)), + + %% check that ebin/app.appup exists + ?assertMatch(true, filelib:is_regular("ebin/app.appup")), + retest_log:log(debug, "Generated ebin/app.appup~n", []), + + %% check that ebin/app.appup has expected version + {ok, [{AppVersion, [{UpgradeFrom, _}], [{DowngradeTo, _}]}]} = + file:consult("ebin/app.appup"), + ?assertEqual(AppVersion, "2.1.2"), + ?assertEqual(UpgradeFrom, "2.1.1"), + ?assertEqual(DowngradeTo, "2.1.1"), + ok. + +has_line([], _RE) -> + false; +has_line([L|T], RE) -> + case re:run(L, RE, []) of + {match, _Captured} -> + true; + match -> + true; + nomatch -> + has_line(T, RE) + end. diff -Nru rebar-2.0.0/inttest/appup_src_script/src/app.app.src rebar-2.6.0/inttest/appup_src_script/src/app.app.src --- rebar-2.0.0/inttest/appup_src_script/src/app.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_script/src/app.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +{application, app, + [{vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}. diff -Nru rebar-2.0.0/inttest/appup_src_script/src/app.appup.src rebar-2.6.0/inttest/appup_src_script/src/app.appup.src --- rebar-2.0.0/inttest/appup_src_script/src/app.appup.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_script/src/app.appup.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +{"1.2.2", + [{"1.2.1", + [{update,proc1,{advanced,[]},[]}, + {update,proc2,{advanced,[]},[]}] + }], + [{"1.2.1", []}]}. diff -Nru rebar-2.0.0/inttest/appup_src_script/src/app.appup.src.script rebar-2.6.0/inttest/appup_src_script/src/app.appup.src.script --- rebar-2.0.0/inttest/appup_src_script/src/app.appup.src.script 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_script/src/app.appup.src.script 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,9 @@ +%% decompose the appup +[{AppVersion, + [{UpgradeFrom, UpgradeInstructions}], + [{DowngradeTo, DowngradeInstructions}]}] = CONFIG, + +%% and replace the version strings +[{"2.1.2", + [{"2.1.1", UpgradeInstructions}], + [{"2.1.1", DowngradeInstructions}]}]. diff -Nru rebar-2.0.0/inttest/appup_src_script/src/appup_src.erl rebar-2.6.0/inttest/appup_src_script/src/appup_src.erl --- rebar-2.0.0/inttest/appup_src_script/src/appup_src.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/appup_src_script/src/appup_src.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(appup_src). + +-compile(export_all). + +test() -> + ok. diff -Nru rebar-2.0.0/inttest/bug_5_rt.erl rebar-2.6.0/inttest/bug_5_rt.erl --- rebar-2.0.0/inttest/bug_5_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/bug_5_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(bug_5_rt). -compile(export_all). diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl rebar-2.6.0/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl --- rebar-2.0.0/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/code_path_no_recurse_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,45 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(code_path_no_recurse_rt). +-export([files/0, + run/1]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "src", "src"}, + {copy, "test", "test"}, + {copy, "deps", "deps"} + ]. + +run(_Dir) -> + retest:log(info, "Compile project~n"), + {ok, _} = retest:sh("./rebar -v compile"), + retest:log(info, "Run eunit with referenced deps on the code path~n"), + {ok, _} = retest:sh("./rebar -v eunit"), + ok. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.app.src rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.app.src --- rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +{application, bazdep, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {bazdep, []}}, + {env, []} + ]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.erl rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.erl --- rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/src/bazdep.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(bazdep). + +-export([bazdep/0]). + +bazdep() -> + bazdep. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/test/bazdep_tests.erl rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/test/bazdep_tests.erl --- rebar-2.0.0/inttest/code_path_no_recurse/deps/bazdep/test/bazdep_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/bazdep/test/bazdep_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +-module(bazdep_tests). +-include_lib("eunit/include/eunit.hrl"). + +bazdep_test() -> + ?assert(bazdep:bazdep() =:= bazdep). diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/rebar.config rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/rebar.config --- rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{bazdep, "1"}]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.app.src rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.app.src --- rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +{application, foodep, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {foodep, []}}, + {env, []} + ]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.erl rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.erl --- rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/src/foodep.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(foodep). + +-export([foodep/0]). + +foodep() -> + bazdep:bazdep() =:= bazdep. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/test/foodep_tests.erl rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/test/foodep_tests.erl --- rebar-2.0.0/inttest/code_path_no_recurse/deps/foodep/test/foodep_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/foodep/test/foodep_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +-module(foodep_tests). +-include_lib("eunit/include/eunit.hrl"). + +foodep_test() -> + ?assert(foodep:foodep()). diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.app.src rebar-2.6.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.app.src --- rebar-2.0.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +{application, unuseddep, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {unuseddep, []}}, + {env, []} + ]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.erl rebar-2.6.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.erl --- rebar-2.0.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/deps/unuseddep/src/unuseddep.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(unuseddep). + +-export([unuseddep/0]). + +unuseddep() -> + unuseddep. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/rebar.config rebar-2.6.0/inttest/code_path_no_recurse/rebar.config --- rebar-2.0.0/inttest/code_path_no_recurse/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{foodep, "1"}]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/src/codepath.app.src rebar-2.6.0/inttest/code_path_no_recurse/src/codepath.app.src --- rebar-2.0.0/inttest/code_path_no_recurse/src/codepath.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/src/codepath.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +{application, codepath, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {codepath, []}}, + {env, []} + ]}. diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/src/codepath.erl rebar-2.6.0/inttest/code_path_no_recurse/src/codepath.erl --- rebar-2.0.0/inttest/code_path_no_recurse/src/codepath.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/src/codepath.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(codepath). + +-export([codepath/0]). + +codepath() -> + foodep:foodep(). diff -Nru rebar-2.0.0/inttest/code_path_no_recurse/test/codepath_tests.erl rebar-2.6.0/inttest/code_path_no_recurse/test/codepath_tests.erl --- rebar-2.0.0/inttest/code_path_no_recurse/test/codepath_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/code_path_no_recurse/test/codepath_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +-module(codepath_tests). +-include_lib("eunit/include/eunit.hrl"). + +codepath_test() -> + ?assertEqual({module, codepath}, code:ensure_loaded(codepath)), + ?assertEqual({module, foodep}, code:ensure_loaded(foodep)), + ?assertEqual({module, bazdep}, code:ensure_loaded(bazdep)), + ?assert(codepath:codepath()). + +unuseddep_test() -> + ?assertEqual(non_existing, code:which(unuseddep)), + ?assertEqual({error, nofile}, code:ensure_loaded(unuseddep)). diff -Nru rebar-2.0.0/inttest/cover/cover_rt.erl rebar-2.6.0/inttest/cover/cover_rt.erl --- rebar-2.0.0/inttest/cover/cover_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/cover/cover_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,76 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Brian H. Ward +%% +%% 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(cover_rt). + +-export([files/0,run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{create, "ebin/foo.app", app(foo)}, + {copy, "../../rebar","rebar"}, + {copy, "src", "src"}, + {copy, + "rebar-cover_export_json.config", + "rebar-cover_export_json.config"}]. + +run(_Dir) -> + ifdef_test(), + cover_export_json_test(), + ok. + +ifdef_test() -> + {ok, Output} = retest:sh("./rebar -v eunit"), + io:format("output => ~p~n", [Output]), + ?assert(check_output(Output, "foo")), + {ok, Listing} = file:list_dir(".eunit"), + ?assert(check_output(Listing, "foo.beam")), + ?assertMatch({ok,_}, retest:sh("./rebar clean")). + +cover_export_json_test() -> + {ok, Output} = + retest:sh("./rebar -v -C rebar-cover_export_json.config eunit"), + ?assert(check_output(Output, "foo")), + ?assertEqual( + {ok, <<"{\"module\":\"foo\",\"covered\":2,\"not_covered\":1}">>}, + file:read_file(".eunit/foo.COVER.json")), + ?assertMatch( + {ok, _}, + retest:sh("./rebar -C rebar-cover_export_json.config clean")). + +check_output(Output,Target) -> + lists:any(fun(Line) -> + string:str(Line, Target) > 0 + end, Output). +app(Name) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/cover/rebar-cover_export_json.config rebar-2.6.0/inttest/cover/rebar-cover_export_json.config --- rebar-2.0.0/inttest/cover/rebar-cover_export_json.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/cover/rebar-cover_export_json.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +{cover_enabled, true}. +{cover_export_json, true}. diff -Nru rebar-2.0.0/inttest/cover/src/foo.erl rebar-2.6.0/inttest/cover/src/foo.erl --- rebar-2.0.0/inttest/cover/src/foo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/cover/src/foo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,18 @@ +-module(foo). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +covered_function() -> + "I am tested". + +uncovered_function() -> + "I am not tested". + +-ifdef(EUNIT). + +covered_function_test() -> + ?assertEqual("I am tested", covered_function()). + +-endif. diff -Nru rebar-2.0.0/inttest/ct1/app.config rebar-2.6.0/inttest/ct1/app.config --- rebar-2.0.0/inttest/ct1/app.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct1/app.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +%% This file is an application config file, not a CT test config file +[{a1, [{foo, bar}]}]. diff -Nru rebar-2.0.0/inttest/ct1/ct1_rt.erl rebar-2.6.0/inttest/ct1/ct1_rt.erl --- rebar-2.0.0/inttest/ct1/ct1_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/ct1/ct1_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(ct1_rt). -compile(export_all). @@ -7,10 +9,12 @@ [{create, "ebin/a1.app", app(a1)}, {copy, "../../rebar", "rebar"}, {copy, "rebar.config", "rebar.config"}, + {copy, "app.config", "app.config"}, {copy, "test_SUITE.erl", "itest/test_SUITE.erl"}]. run(_Dir) -> {ok, _} = retest:sh("./rebar compile ct"), + {ok, _} = retest:sh("./rebar compile ct -v"), ok. diff -Nru rebar-2.0.0/inttest/ct1/rebar.config rebar-2.6.0/inttest/ct1/rebar.config --- rebar-2.0.0/inttest/ct1/rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/ct1/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1,2 @@ - {ct_dir, "itest"}. +{ct_extra_params, "-repeat 2 -erl_args -config app"}. diff -Nru rebar-2.0.0/inttest/ct1/test_SUITE.erl rebar-2.6.0/inttest/ct1/test_SUITE.erl --- rebar-2.0.0/inttest/ct1/test_SUITE.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/ct1/test_SUITE.erl 2015-06-19 16:14:28.000000000 +0000 @@ -5,7 +5,13 @@ -include_lib("ct.hrl"). all() -> - [simple_test]. + [simple_test, + app_config_file_test]. simple_test(Config) -> io:format("Test: ~p\n", [Config]). + +app_config_file_test(_Config) -> + application:start(a1), + {ok, bar} = application:get_env(a1, foo), + application:stop(a1). diff -Nru rebar-2.0.0/inttest/ct2/ct2_rt.erl rebar-2.6.0/inttest/ct2/ct2_rt.erl --- rebar-2.0.0/inttest/ct2/ct2_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/ct2/ct2_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(ct2_rt). -compile(export_all). @@ -6,11 +8,17 @@ 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, "deps/bar.test.spec", "deps/bar.test.spec"}, {copy, "foo_SUITE.erl", "test/foo_SUITE.erl"}]. run(_Dir) -> - {ok, _} = retest:sh("./rebar compile ct -v"), + Ref = retest:sh("./rebar compile ct -vvv", [async]), + {ok, [[CTRunCmd]]} = retest:sh_expect(Ref, "^\"ct_run.*", + [global, {capture, first, binary}]), + {match, _} = re:run(CTRunCmd, "foo.test.spec", [global]), + %% deps/bar.test.spec should be ignored by rebar_ct:collect_glob/3 + nomatch = re:run(CTRunCmd, "bar.test.spec", [global]), ok. %% diff -Nru rebar-2.0.0/inttest/ct2/deps/bar.test.spec rebar-2.6.0/inttest/ct2/deps/bar.test.spec --- rebar-2.0.0/inttest/ct2/deps/bar.test.spec 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct2/deps/bar.test.spec 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +%% this test spec should be ignored diff -Nru rebar-2.0.0/inttest/ct3/app.config rebar-2.6.0/inttest/ct3/app.config --- rebar-2.0.0/inttest/ct3/app.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct3/app.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +%% This file is an application config file, not a CT test config file +[{a1, [{foo, bar}]}]. diff -Nru rebar-2.0.0/inttest/ct3/ct3_rt.erl rebar-2.6.0/inttest/ct3/ct3_rt.erl --- rebar-2.0.0/inttest/ct3/ct3_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct3/ct3_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,56 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Savchuk Igor +%% +%% 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(ct3_rt). + +-compile(export_all). + + +files() -> + [{create, "ebin/a1.app", app(a1)}, + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "app.config", "itest/app.config"}, + {copy, "test_SUITE.erl", "itest/test_SUITE.erl"}, + {copy, "converted"}, + {copy, "unconverted"}]. + +run(_Dir) -> + {ok, _} = retest:sh("./rebar compile ct -v", + [{env, [{"ERL_FLAGS", "-name ct_rt3"}]}]), + ok. + +%% +%% Generate the contents of a simple .app file +%% +app(Name) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/ct3/rebar.config rebar-2.6.0/inttest/ct3/rebar.config --- rebar-2.0.0/inttest/ct3/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct3/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +{ct_dir, "itest"}. +{ct_extra_params, "-repeat 2 -pa converted -erl_args -pa unconverted"}. diff -Nru rebar-2.0.0/inttest/ct3/test_SUITE.erl rebar-2.6.0/inttest/ct3/test_SUITE.erl --- rebar-2.0.0/inttest/ct3/test_SUITE.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/ct3/test_SUITE.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,52 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Savchuk Igor +%% +%% 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(test_SUITE). + +-compile(export_all). + +-include_lib("ct.hrl"). + +all() -> + [simple_test, + app_config_file_test, + check_path]. + +simple_test(Config) -> + io:format("Test: ~p\n", [Config]). + +app_config_file_test(_Config) -> + application:start(a1), + {ok, bar} = application:get_env(a1, foo), + application:stop(a1). + +%% check if -erl_args in ct_extra_params of rebar.config is working +%% if it is, path before -erl_args is converted to absolute +%% and path after -erl_args is not +check_path(_Config) -> + Path = code:get_path(), + false = lists:member("converted", Path), + true = lists:member("unconverted", Path). diff -Nru rebar-2.0.0/inttest/depplugins/base_dir_cwd_plugin.erl rebar-2.6.0/inttest/depplugins/base_dir_cwd_plugin.erl --- rebar-2.0.0/inttest/depplugins/base_dir_cwd_plugin.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/base_dir_cwd_plugin.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,7 @@ +-module(base_dir_cwd_plugin). +-export([pre_compile/2]). + +pre_compile(_, _) -> + File = "base_dir_cwd_pre.compile", + ok = file:write_file(File, <<"base_dir cwd pre_compile plugin">>), + rebar_log:log(info, "Wrote ~p/~s~n", [rebar_utils:get_cwd(), File]). diff -Nru rebar-2.0.0/inttest/depplugins/dep_cwd_plugin.erl rebar-2.6.0/inttest/depplugins/dep_cwd_plugin.erl --- rebar-2.0.0/inttest/depplugins/dep_cwd_plugin.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/dep_cwd_plugin.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,7 @@ +-module(dep_cwd_plugin). +-export([pre_compile/2]). + +pre_compile(_, _) -> + File = "dep_cwd_pre.compile", + ok = file:write_file(File, <<"dep cwd pre_compile plugin">>), + rebar_log:log(info, "Wrote ~p/~s~n", [rebar_utils:get_cwd(), File]). diff -Nru rebar-2.0.0/inttest/depplugins/depplugins_rt.erl rebar-2.6.0/inttest/depplugins/depplugins_rt.erl --- rebar-2.0.0/inttest/depplugins/depplugins_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/depplugins_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,71 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%%% @doc Plugin handling test +%%% +%%% This test checks if plugins are loaded correctly. +%%% +%%% It has three applications: +%%%
    +%%%
  1. fish. top-level app, has one dependency: `dependsonplugin'. +%%% It also loads a plugin from CWD which creates +%%% base_dir_cwd_pre.compile on pre_compile.
  2. +%%%
  3. dependsonplugin, has one dependency: `testplugin' and loads +%%% the testplugin_mod plugin.
  4. +%%%
  5. testplugin. This is a plugin application which creates +%%% plugin_pre.compile on pre_compile. It also loads a plugin from CWD +%%% which creates dep_cwd_pre.compile on pre_compile.
  6. +%%%
+ +-module(depplugins_rt). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "base_dir_cwd_plugin.erl", "base_dir_cwd_plugin.erl"}, + {create, "ebin/fish.app", app(fish, [])}, + + {copy, "rebar_dependsonplugin.config", + "deps/dependsonplugin/rebar.config"}, + {create, "deps/dependsonplugin/ebin/dependsonplugin.app", + app(dependsonplugin, [])}, + + {copy, "rebar_testplugin.config", "deps/testplugin/rebar.config"}, + {copy, "testplugin_mod.erl", + "deps/testplugin/plugins/testplugin_mod.erl"}, + {copy, "dep_cwd_plugin.erl", "deps/testplugin/dep_cwd_plugin.erl"}, + {create, "deps/testplugin/ebin/testplugin.app", app(testplugin, [])} + ]. + +run(_Dir) -> + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + + ?assertEqual(true, filelib:is_regular("base_dir_cwd_pre.compile")), + + ?assertEqual(true, filelib:is_regular( + "deps/dependsonplugin/base_dir_cwd_pre.compile")), + ?assertEqual(true, filelib:is_regular( + "deps/dependsonplugin/plugin_pre.compile")), + + ?assertEqual(true, filelib:is_regular( + "deps/testplugin/base_dir_cwd_pre.compile")), + ?assertEqual(true, filelib:is_regular( + "deps/testplugin/dep_cwd_pre.compile")), + ?assertEqual(true, filelib:is_regular( + "deps/testplugin/plugin_pre.compile")), + ok. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/depplugins/rebar.config rebar-2.6.0/inttest/depplugins/rebar.config --- rebar-2.0.0/inttest/depplugins/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +{deps, [dependsonplugin]}. +{plugins, [base_dir_cwd_plugin]}. diff -Nru rebar-2.0.0/inttest/depplugins/rebar_dependsonplugin.config rebar-2.6.0/inttest/depplugins/rebar_dependsonplugin.config --- rebar-2.0.0/inttest/depplugins/rebar_dependsonplugin.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/rebar_dependsonplugin.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +{deps, [testplugin]}. +{plugins, [testplugin_mod]}. diff -Nru rebar-2.0.0/inttest/depplugins/rebar_testplugin.config rebar-2.6.0/inttest/depplugins/rebar_testplugin.config --- rebar-2.0.0/inttest/depplugins/rebar_testplugin.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/rebar_testplugin.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{plugins, [dep_cwd_plugin]}. diff -Nru rebar-2.0.0/inttest/depplugins/testplugin_mod.erl rebar-2.6.0/inttest/depplugins/testplugin_mod.erl --- rebar-2.0.0/inttest/depplugins/testplugin_mod.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/depplugins/testplugin_mod.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,7 @@ +-module(testplugin_mod). +-export([pre_compile/2]). + +pre_compile(_, _) -> + File = "plugin_pre.compile", + ok = file:write_file(File, <<"Yadda!">>), + rebar_log:log(info, "Wrote ~p/~s~n", [rebar_utils:get_cwd(), File]). diff -Nru rebar-2.0.0/inttest/erlc/asn1/SIMPLE-ASN.asn1 rebar-2.6.0/inttest/erlc/asn1/SIMPLE-ASN.asn1 --- rebar-2.0.0/inttest/erlc/asn1/SIMPLE-ASN.asn1 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/asn1/SIMPLE-ASN.asn1 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,7 @@ +SIMPLE-ASN DEFINITIONS ::= BEGIN + + SimpleMessage ::= SEQUENCE { + id INTEGER + } + +END diff -Nru rebar-2.0.0/inttest/erlc/erlc_rt.erl rebar-2.6.0/inttest/erlc/erlc_rt.erl --- rebar-2.0.0/inttest/erlc/erlc_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/erlc_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,163 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(erlc_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(MODULES, + [after_first_erl, + first_xrl, + first_yrl, + first_erl, + foo, + foo_app, + foo_sup, + foo_test_worker, + foo_worker, + 'SIMPLE-ASN']). + +-define(BEAM_FILES, + ["after_first_erl.beam", + "first_xrl.beam", + "first_yrl.beam", + "first_erl.beam", + "foo.beam", + "foo_app.beam", + "foo_sup.beam", + "foo_test_worker.beam", + "foo_worker.beam", + "SIMPLE-ASN.beam"]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "rebar-no_debug_info.config", "rebar-no_debug_info.config"}, + {copy, "include", "include"}, + {copy, "extra-include", "extra-include"}, + {copy, "src", "src"}, + {copy, "extra-src", "extra-src"}, + {copy, "mibs", "mibs"}, + {copy, "asn1", "asn1"}, + {create, "ebin/foo.app", app(foo, ?MODULES)}, + %% deps + {create, "deps/foobar/ebin/foobar.app", app(foobar, [foobar])}, + {copy, "foobar.erl", "deps/foobar/src/foobar.erl"} + ]. + +run(_Dir) -> + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + ok = check_beams(true), + ok = check_debug_info(true), + MibResult = filename:join(["priv", "mibs", "SIMPLE-MIB.bin"]), + ?assertMatch(true, filelib:is_regular(MibResult)), + ?assertMatch({ok, _}, retest_sh:run("./rebar clean", [])), + ok = check_beams(false), + ?assertMatch(false, filelib:is_regular(MibResult)), + ?assertMatch( + {ok, _}, + retest_sh:run("./rebar -C rebar-no_debug_info.config compile", [])), + ok = check_beams(true), + ok = check_debug_info(false), + ?assertMatch(true, filelib:is_regular(MibResult)), + %% Regression test for https://github.com/rebar/rebar/issues/249 + %% + %% Root cause: We didn't have per-project .rebar/erlcinfo but just one in + %% /.rebar/erlcinfo. + %% + %% Solution: Ensure every project has its own .rebar/erlcinfo + %% + %% For the bug to happen, the following conditions must be met: + %% + %% 1. /rebar.config has erl_first_files + %% 2. one of the 'first' files depends on another file (in this + %% case via -include_lib()) + %% 3. a sub project's rebar.config, if any, has no erl_first_files entry + %% + %% Now because erl_first_files is retrieved via rebar_config:get_list/3, + %% base_dir/rebar.config's erl_first_files is inherited, and because we had + %% a shared /.rebar/erlcinfo instead of one per project, the + %% cached entry was reused. Next, while compiling the sub project + %% rebar_erlc_compiler:needs_compile/3 gets a last modification time of + %% zero for the 'first' file which does not exist inside the sub project. + %% This, and the fact that it has at least one dependency, makes + %% needs_compile/3 return 'true'. The root cause is that we didn't have per + %% project .rebar/erlcinfo. For /.rebar/erlcinfo to be populated, + %% base_dir has to be compiled at least once. Therefore, after the first + %% compile any compile processing the sub project will fail because + %% needs_compile/3 will always return true for the non-existent 'first' + %% file. + ?assertMatch({ok, _}, retest_sh:run("./rebar clean", [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + ok = check_beams(true), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + ok = check_beams(true), + ok. + +check_beams(Exist) -> + check_files(Exist, fun filelib:is_regular/1). + +check_debug_info(HasDebugInfo) -> + check_files(HasDebugInfo, fun has_debug_info/1). + +check_files(Expected, Check) -> + lists:foreach( + fun(F) -> + File = filename:join("ebin", F), + ?assertEqual(Expected, Check(File)) + end, + ?BEAM_FILES). + +%% NOTE: Copied from dialyzer_utils:get_abstract_code_from_beam/1 and +%% modified for local use. We could have called the function directly, +%% but dialyzer_utils is not an official API to rely on. +has_debug_info(File) -> + case beam_lib:chunks(File, [abstract_code]) of + {ok, {_Mod, List}} -> + case lists:keyfind(abstract_code, 1, List) of + {abstract_code, {raw_abstract_v1, _Abstr}} -> + true; + _ -> + false + end; + _ -> + false + end. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/erlc/extra-include/foo_extra.hrl rebar-2.6.0/inttest/erlc/extra-include/foo_extra.hrl --- rebar-2.0.0/inttest/erlc/extra-include/foo_extra.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/extra-include/foo_extra.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-define(FOO_EXTRA, foo_extra). diff -Nru rebar-2.0.0/inttest/erlc/extra-src/after_first_erl.erl rebar-2.6.0/inttest/erlc/extra-src/after_first_erl.erl --- rebar-2.0.0/inttest/erlc/extra-src/after_first_erl.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/extra-src/after_first_erl.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,11 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(after_first_erl). +-compile({parse_transform, first_erl}). + +-include_lib("eunit/include/eunit.hrl"). + +-export([test/0]). + +test() -> + ?debugHere. diff -Nru rebar-2.0.0/inttest/erlc/extra-src/foo_sup.erl rebar-2.6.0/inttest/erlc/extra-src/foo_sup.erl --- rebar-2.0.0/inttest/erlc/extra-src/foo_sup.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/extra-src/foo_sup.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,15 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(foo_sup). + +-behavior(supervisor). + +-export([start_link/0, + init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init(_Args) -> + FooChild = {foo,{foo, start_link, []}, permanent, 5000, worker, [foo]}, + {ok,{{one_for_all,1,1}, [FooChild]}}. diff -Nru rebar-2.0.0/inttest/erlc/foobar.erl rebar-2.6.0/inttest/erlc/foobar.erl --- rebar-2.0.0/inttest/erlc/foobar.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/foobar.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,8 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(foobar). + +-export([test/0]). + +test() -> + true. diff -Nru rebar-2.0.0/inttest/erlc/include/foo_core.hrl rebar-2.6.0/inttest/erlc/include/foo_core.hrl --- rebar-2.0.0/inttest/erlc/include/foo_core.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/include/foo_core.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-define(FOO_CORE, foo_core). diff -Nru rebar-2.0.0/inttest/erlc/mibs/SIMPLE-MIB.mib rebar-2.6.0/inttest/erlc/mibs/SIMPLE-MIB.mib --- rebar-2.0.0/inttest/erlc/mibs/SIMPLE-MIB.mib 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/mibs/SIMPLE-MIB.mib 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,26 @@ +-- SIMPLE-MIB. +-- This is just a simple MIB used for testing! +-- + + +SIMPLE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, enterprises + FROM SNMPv2-SMI; + +ericsson MODULE-IDENTITY + LAST-UPDATED + "201403060000Z" + ORGANIZATION + "rebar" + CONTACT-INFO + "rebar + or + whoever is currently responsible for the SIMPLE + enterprise MIB tree branch (enterprises.999)." + DESCRIPTION + "This very small module is made available + for mib-compilation testing." + ::= { enterprises 999 } +END diff -Nru rebar-2.0.0/inttest/erlc/rebar.config rebar-2.6.0/inttest/erlc/rebar.config --- rebar-2.0.0/inttest/erlc/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,13 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +{erl_first_files, + ["src/first_xrl.erl", "src/first_yrl.erl", "src/first_erl.erl", "extra-src/after_first_erl.erl"]}. + +{deps, [foobar]}. + +{erl_opts, + [ + {i, "extra-include"}, + {src_dirs, ["extra-src", "src"]}, + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. diff -Nru rebar-2.0.0/inttest/erlc/rebar-no_debug_info.config rebar-2.6.0/inttest/erlc/rebar-no_debug_info.config --- rebar-2.0.0/inttest/erlc/rebar-no_debug_info.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/rebar-no_debug_info.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,11 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +{erl_first_files, ["src/first_xrl.erl", "src/first_yrl.erl"]}. + +{erl_opts, + [ + no_debug_info, + {i, "extra-include"}, + {src_dirs, ["src", "extra-src"]}, + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. diff -Nru rebar-2.0.0/inttest/erlc/src/behaviour/foo_worker.erl rebar-2.6.0/inttest/erlc/src/behaviour/foo_worker.erl --- rebar-2.0.0/inttest/erlc/src/behaviour/foo_worker.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/behaviour/foo_worker.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,14 @@ +-module(foo_worker). + +-ifdef(NO_CALLBACK_ATTRIBUTE). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> [{status, 0}]; +behaviour_info(_) -> undefined. + +-else. + +-callback status() -> 'idle' | 'busy'. + +-endif. diff -Nru rebar-2.0.0/inttest/erlc/src/._do_not_compile.erl rebar-2.6.0/inttest/erlc/src/._do_not_compile.erl --- rebar-2.0.0/inttest/erlc/src/._do_not_compile.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/._do_not_compile.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +syntax error +this file is here to verify that rebar does not try to +compile files like OS X resource forks and should not be +processed at all diff -Nru rebar-2.0.0/inttest/erlc/src/first_erl.erl rebar-2.6.0/inttest/erlc/src/first_erl.erl --- rebar-2.0.0/inttest/erlc/src/first_erl.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/first_erl.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,13 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(first_erl). + +-include_lib("eunit/include/eunit.hrl"). + +-export([test/0, parse_transform/2]). + +test() -> + ?debugHere. + +parse_transform(Forms, _Options) -> + Forms. diff -Nru rebar-2.0.0/inttest/erlc/src/first_xrl.xrl rebar-2.6.0/inttest/erlc/src/first_xrl.xrl --- rebar-2.0.0/inttest/erlc/src/first_xrl.xrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/first_xrl.xrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,13 @@ +Definitions. + +D = [0-9] + +Rules. + +{D}+ : + {token,{integer,TokenLine,list_to_integer(TokenChars)}}. + +{D}+\.{D}+((E|e)(\+|\-)?{D}+)? : + {token,{float,TokenLine,list_to_float(TokenChars)}}. + +Erlang code. diff -Nru rebar-2.0.0/inttest/erlc/src/first_yrl.yrl rebar-2.6.0/inttest/erlc/src/first_yrl.yrl --- rebar-2.0.0/inttest/erlc/src/first_yrl.yrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/first_yrl.yrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,9 @@ +Nonterminals list elements element. +Terminals atom '(' ')'. +Rootsymbol list. +list -> '(' ')'. +list -> '(' elements ')'. +elements -> element. +elements -> element elements. +element -> atom. +element -> list. diff -Nru rebar-2.0.0/inttest/erlc/src/foo_app.erl rebar-2.6.0/inttest/erlc/src/foo_app.erl --- rebar-2.0.0/inttest/erlc/src/foo_app.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/foo_app.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,10 @@ +-module(foo_app). + +-behaviour(application). + +-export([start/2, + stop/1]). + +start(_Type, _Args) -> foo_sup:start_link(). + +stop(_State) -> ok. diff -Nru rebar-2.0.0/inttest/erlc/src/foo.erl rebar-2.6.0/inttest/erlc/src/foo.erl --- rebar-2.0.0/inttest/erlc/src/foo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/foo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,35 @@ +-module(foo). + +-export([start_link/0, + start_link/1, + init/1, + terminate/2, + handle_info/2, + handle_call/3, + handle_cast/2, + code_change/3]). + +-behavior(gen_server). + +-include("foo_core.hrl"). +-include("foo_extra.hrl"). +-include_lib("kernel/include/file.hrl"). + +-record(state, {node :: node()}). + +start_link() -> start_link(undefined). + +start_link(Args) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []). + +init(_Args) -> {ok, #state{node=node()}}. + +terminate(_Reason, _Data) -> ok. + +handle_info(_Info, State) -> {noreply, State}. + +handle_cast(_Msg, State) -> {noreply, State}. + +handle_call(_Msg, _From, State) -> {reply, ok, State}. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. diff -Nru rebar-2.0.0/inttest/erlc/src/foo_test_worker.erl rebar-2.6.0/inttest/erlc/src/foo_test_worker.erl --- rebar-2.0.0/inttest/erlc/src/foo_test_worker.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc/src/foo_test_worker.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,34 @@ +-module(foo_test_worker). + +-behaviour(gen_server). +-behaviour(foo_worker). + +-export([start_link/0, + start_link/1, + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3, + status/0]). + +-include_lib("kernel/include/inet.hrl"). + +start_link() -> start_link(undefined). + +start_link(Args) -> gen_server:start_link(?MODULE, Args, []). + +init([]) -> {ok, undefined}. + +handle_call(_Event, _From, State) -> {reply, ok, State}. + +handle_cast(_Event, State) -> {noreply, State}. + +handle_info(_Info, State) -> {noreply, State}. + +terminate(_Reason, _State) -> ok. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. + +status() -> busy. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/erlc_dep_graph_rt.erl rebar-2.6.0/inttest/erlc_dep_graph/erlc_dep_graph_rt.erl --- rebar-2.0.0/inttest/erlc_dep_graph/erlc_dep_graph_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/erlc_dep_graph_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,111 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 David Kubecka +%% +%% 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(erlc_dep_graph_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "src", "src"}, + {copy, "include", "include"}, + {copy, "extra_include", "extra_include"}]. + +run(_Dir) -> + compile_all(ok, ""), + check_beams_ok(), + check_beams_untouched(filelib:wildcard("ebin/*.beam")), + modify_and_recompile_ok("src/lisp.erl", "ebin/lisp.beam"), + + clean_all_ok(), + compile_all(error, "-C rebar.config.non-existing"), + compile_all(ok, ""), + modify_and_recompile_ok("extra_include/extra.hrl", "ebin/java.beam"), + + Java = "src/java.erl", + {ok, OrigContent} = file:read_file(Java), + %% Remove header file inclusion + {ok, _} = file:copy("src/java.erl.no_extra", Java), + %% Ensure recompilation + touch([Java]), + compile_all(ok, ""), + %% Modify that header file + touch(["extra_include/extra.hrl"]), + %% Ensure we don't have to recompile anything + check_beams_untouched(["ebin/java.beam"]), + %% Clean up + ok = file:write_file(Java, OrigContent), + + %% Check that changes propagate deeply through the dependency tree + modify_and_recompile_ok("include/lambda.hrl", "ebin/perl.beam"), + + ok. + +check_beams_ok() -> + F = fun(BeamFile) -> ?assert(filelib:is_regular(BeamFile)) end, + with_erl_beams(F). + +check_beams_untouched(Beams) -> + compile_all_and_assert_mtimes(Beams, fun erlang:'=:='/2). + +modify_and_recompile_ok(TouchFile, CheckFile) -> + touch([TouchFile]), + compile_all_and_assert_mtimes([CheckFile], fun erlang:'<'/2). + +compile_all_and_assert_mtimes(Beams, Cmp) -> + BeamsModifiedBefore = mtime_ns(Beams), + compile_all(ok, ""), + BeamsModifiedAfter = mtime_ns(Beams), + lists:zipwith(fun(Before, After) -> ?assert(Cmp(Before, After)) end, + BeamsModifiedBefore, BeamsModifiedAfter). + +with_erl_beams(F) -> + lists:map( + fun(ErlFile) -> + ErlRoot = filename:rootname(filename:basename(ErlFile)), + BeamFile = filename:join("ebin", ErlRoot ++ ".beam"), + F(BeamFile) + end, + filelib:wildcard("src/*.erl")). + +mtime_ns(Files) -> + [os:cmd("stat -c%y " ++ File) || File <- Files]. + +touch(Files) -> + %% Sleep one second so that filelib:last_modified/1 is guaranteed to notice + %% that files have changed. + ok = timer:sleep(1000), + [os:cmd("touch " ++ File) || File <- Files]. + +compile_all(Result, Opts) -> + ?assertMatch({Result, _}, + retest_sh:run("./rebar " ++ Opts ++ " compile", [])). + +clean_all_ok() -> + ?assertMatch({ok, _}, retest_sh:run("./rebar clean", [])). diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/extra_include/extra.hrl rebar-2.6.0/inttest/erlc_dep_graph/extra_include/extra.hrl --- rebar-2.0.0/inttest/erlc_dep_graph/extra_include/extra.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/extra_include/extra.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-define(CONCISE, impossible). diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/include/lambda.hrl rebar-2.6.0/inttest/erlc_dep_graph/include/lambda.hrl --- rebar-2.0.0/inttest/erlc_dep_graph/include/lambda.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/include/lambda.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-define(FUN, fake). diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/rebar.config rebar-2.6.0/inttest/erlc_dep_graph/rebar.config --- rebar-2.0.0/inttest/erlc_dep_graph/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,8 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +{erl_opts, + [ + {i, "extra_include"}, + {parse_transform, lisp}, + {parse_transform, pascal} + ]}. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/foo.app.src rebar-2.6.0/inttest/erlc_dep_graph/src/foo.app.src --- rebar-2.0.0/inttest/erlc_dep_graph/src/foo.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/foo.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,7 @@ +{application,foo, + [{description,[]}, + {vsn,"1.0.0"}, + {registered,[]}, + {applications,[kernel,stdlib]}, + {env,[]} + ]}. \ No newline at end of file diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/java.erl rebar-2.6.0/inttest/erlc_dep_graph/src/java.erl --- rebar-2.0.0/inttest/erlc_dep_graph/src/java.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/java.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,11 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(java). + +-export([factory/0]). + +-include("lambda.hrl"). +-include("extra.hrl"). + +factory() -> + ?FUN. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/java.erl.no_extra rebar-2.6.0/inttest/erlc_dep_graph/src/java.erl.no_extra --- rebar-2.0.0/inttest/erlc_dep_graph/src/java.erl.no_extra 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/java.erl.no_extra 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,10 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(java). + +-export([factory/0]). + +-include("lambda.hrl"). + +factory() -> + ?FUN. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/lisp.erl rebar-2.6.0/inttest/erlc_dep_graph/src/lisp.erl --- rebar-2.0.0/inttest/erlc_dep_graph/src/lisp.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/lisp.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,13 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(lisp). + +-export([parse_transform/2]). + +-include("lambda.hrl"). +-ifdef(NOT_DEFINED). +-include_lib("include/non/existent.hrl"). +-endif. + +parse_transform(Forms, _Options) -> + Forms. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/pascal.erl rebar-2.6.0/inttest/erlc_dep_graph/src/pascal.erl --- rebar-2.0.0/inttest/erlc_dep_graph/src/pascal.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/pascal.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,8 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(pascal). + +-export([parse_transform/2]). + +parse_transform(Forms, _Options) -> + Forms. diff -Nru rebar-2.0.0/inttest/erlc_dep_graph/src/perl.erl rebar-2.6.0/inttest/erlc_dep_graph/src/perl.erl --- rebar-2.0.0/inttest/erlc_dep_graph/src/perl.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/erlc_dep_graph/src/perl.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,10 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +-module(perl). + +-export(['$_'/0]). + +-compile({parse_transform, lisp}). + +'$_'() -> + anything. diff -Nru rebar-2.0.0/inttest/eunit/eunit_rt.erl rebar-2.6.0/inttest/eunit/eunit_rt.erl --- rebar-2.0.0/inttest/eunit/eunit_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/eunit/eunit_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,50 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(eunit_rt). +-export([files/0, run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [{create, "ebin/foo.app", app(foo)}, + {copy, "../../rebar", "rebar"}, + {copy, "src", "src"}, + {copy, "eunit_src", "eunit_src"}, + {copy, + "rebar-eunit_compile_opts.config", + "rebar-eunit_compile_opts.config"}]. + +run(_Dir) -> + ifdef_test(), + eunit_compile_opts_test(), + ok. + +ifdef_test() -> + {ok, Output} = retest:sh("./rebar -v eunit"), + ?assert(check_output(Output, "foo_test")), + ?assertMatch({ok, _}, retest:sh("./rebar clean")). + +eunit_compile_opts_test() -> + {ok, Output} = + retest:sh("./rebar -v -C rebar-eunit_compile_opts.config eunit"), + ?assert(check_output(Output, "bar_test")), + ?assertMatch( + {ok, _}, + retest:sh("./rebar -C rebar-eunit_compile_opts.config clean")). + +check_output(Output, Target) -> + lists:any(fun(Line) -> + string:str(Line, Target) > 0 + end, Output). + +%% +%% Generate the contents of a simple .app file +%% +app(Name) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/eunit/eunit_src/bar.erl rebar-2.6.0/inttest/eunit/eunit_src/bar.erl --- rebar-2.0.0/inttest/eunit/eunit_src/bar.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/eunit/eunit_src/bar.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,6 @@ +-module(bar). + +-include_lib("eunit/include/eunit.hrl"). + +bar_test() -> + ?assert(true). diff -Nru rebar-2.0.0/inttest/eunit/rebar-eunit_compile_opts.config rebar-2.6.0/inttest/eunit/rebar-eunit_compile_opts.config --- rebar-2.0.0/inttest/eunit/rebar-eunit_compile_opts.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/eunit/rebar-eunit_compile_opts.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{eunit_compile_opts, [{src_dirs, ["eunit_src"]}]}. diff -Nru rebar-2.0.0/inttest/eunit/src/foo.erl rebar-2.6.0/inttest/eunit/src/foo.erl --- rebar-2.0.0/inttest/eunit/src/foo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/eunit/src/foo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,10 @@ +-module(foo). + +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + +foo_test() -> + ?assert(true). + +-endif. diff -Nru rebar-2.0.0/inttest/logging/logging_rt.erl rebar-2.6.0/inttest/logging/logging_rt.erl --- rebar-2.0.0/inttest/logging/logging_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/logging/logging_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,125 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(logging_rt). +-export([files/0, + run/1]). + +-define(APP_FILE, "ebin/logging.app"). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {create, ?APP_FILE, app(invalid_name, [])} + ]. + +run(_Dir) -> + SharedExpected = "==> logging_rt \\(compile\\)", + %% provoke ERROR due to an invalid app file + retest:log(info, "Check 'compile' failure output~n"), + ok = check_output("./rebar compile -q", should_fail, + [SharedExpected, "ERROR: "], + ["WARN: ", "INFO: ", "DEBUG: "]), + %% fix bad app file + ok = file:write_file(?APP_FILE, app(logging, [])), + retest:log(info, "Check 'compile' success output~n"), + ok = check_output("./rebar compile", should_succeed, + [SharedExpected], + ["ERROR: ", "WARN: ", "INFO: ", "DEBUG: "]), + retest:log(info, "Check 'compile -v' success output~n"), + ok = check_output("./rebar compile -v", should_succeed, + [SharedExpected], + ["ERROR: ", "INFO: ", "DEBUG: "]), + retest:log(info, "Check 'compile -vv' success output~n"), + ok = check_output("./rebar compile -vv", should_succeed, + [SharedExpected, "DEBUG: "], + ["ERROR: ", "INFO: "]), + ok. + +check_output(Cmd, FailureMode, Expected, Unexpected) -> + case {retest:sh(Cmd), FailureMode} of + {{error, _}=Error, should_succeed} -> + retest:log(error, "cmd '~s' failed:~n~p~n", [Cmd, Error]), + Error; + {{ok, Captured}, should_succeed} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected); + {{error, {stopped, {_Rc, Captured}}}, should_fail} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected) + end. + +check_output1(Cmd, Captured, Expected, Unexpected) -> + ReOpts = [{capture, all, list}], + ExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + retest:log(error, + "Expected pattern '~s' missing " + "in the following output:~n" + "=== BEGIN ===~n~s~n=== END ===~n", + [Pattern, Captured]), + {true, Pattern}; + {match, _} -> + false + end + end, Expected), + + UnExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + false; + {match, [Match]} -> + retest:log( + error, + "Unexpected output when running cmd '~s':~n~s~n", + [Cmd, Match]), + {true, Match} + end + end, Unexpected), + + case {ExMatches, UnExMatches} of + {[], []} -> + ok; + _ -> + error + end. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/port/c_src/test1.c rebar-2.6.0/inttest/port/c_src/test1.c --- rebar-2.0.0/inttest/port/c_src/test1.c 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/port/c_src/test1.c 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +#include "test1.h" diff -Nru rebar-2.0.0/inttest/port/c_src/test2.c rebar-2.6.0/inttest/port/c_src/test2.c --- rebar-2.0.0/inttest/port/c_src/test2.c 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/port/c_src/test2.c 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +#include "test2.h" diff -Nru rebar-2.0.0/inttest/port/c_src/test2.h rebar-2.6.0/inttest/port/c_src/test2.h --- rebar-2.0.0/inttest/port/c_src/test2.h 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/port/c_src/test2.h 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +#include "test1.h" diff -Nru rebar-2.0.0/inttest/port/port_rt.erl rebar-2.6.0/inttest/port/port_rt.erl --- rebar-2.0.0/inttest/port/port_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/port/port_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,100 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tomas Janousek +%% +%% 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(port_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "c_src", "c_src"}, + {create, "ebin/foo.app", app(foo, [])} + ]. + +run(_Dir) -> + %% wait a bit for new files to have different timestamps + wait(), + %% test.so is created during first compile + ?assertEqual(0, filelib:last_modified("priv/test.so")), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + TestSo1 = filelib:last_modified("priv/test.so"), + ?assert(TestSo1 > 0), + wait(), + %% nothing happens during second compile + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + TestSo2 = filelib:last_modified("priv/test.so"), + Test1o2 = filelib:last_modified("c_src/test1.o"), + Test2o2 = filelib:last_modified("c_src/test2.o"), + ?assertEqual(TestSo1, TestSo2), + ?assert(TestSo1 >= Test1o2), + ?assert(TestSo1 >= Test2o2), + wait(), + %% when test2.c changes, at least test2.o and test.so are rebuilt + ?assertMatch({ok, _}, retest_sh:run("touch c_src/test2.c", [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + TestSo3 = filelib:last_modified("priv/test.so"), + Test2o3 = filelib:last_modified("c_src/test2.o"), + ?assert(TestSo3 > TestSo2), + ?assert(Test2o3 > TestSo2), + wait(), + %% when test2.h changes, at least test2.o and test.so are rebuilt + ?assertMatch({ok, _}, retest_sh:run("touch c_src/test2.h", [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + TestSo4 = filelib:last_modified("priv/test.so"), + Test2o4 = filelib:last_modified("c_src/test2.o"), + ?assert(TestSo4 > TestSo3), + ?assert(Test2o4 > TestSo3), + wait(), + %% when test1.h changes, everything is rebuilt + ?assertMatch({ok, _}, retest_sh:run("touch c_src/test1.h", [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + TestSo5 = filelib:last_modified("priv/test.so"), + Test1o5 = filelib:last_modified("c_src/test1.o"), + Test2o5 = filelib:last_modified("c_src/test2.o"), + ?assert(TestSo5 > TestSo4), + ?assert(Test1o5 > TestSo4), + ?assert(Test2o5 > TestSo4), + ok. + +wait() -> + timer:sleep(1000). + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/port/rebar.config rebar-2.6.0/inttest/port/rebar.config --- rebar-2.0.0/inttest/port/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/port/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{port_specs, [{"priv/test.so", ["c_src/*.c"]}]}. diff -Nru rebar-2.0.0/inttest/profile/profile_rt.erl rebar-2.6.0/inttest/profile/profile_rt.erl --- rebar-2.0.0/inttest/profile/profile_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/profile/profile_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,192 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(profile_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +files() -> + [ + {copy, "../../rebar", "rebar"} + ]. + +run(_Dir) -> + Cmd = "./rebar list-deps", + FprofFiles = fprof_files(), + EflameFiles = ["eflame.trace", "eflame.svg"], + + %% run a simple command (list-deps) without profiling + SharedExpected = "==> profile_rt \\(list-deps\\)", + %% run Cmd without profiling + retest:log(info, "Check '~s' without profiling~n", [Cmd]), + ok = check(Cmd, should_succeed, [SharedExpected], ["Profiling!"], + [], FprofFiles ++ EflameFiles), + + %% run Cmd with fprof profiling + retest:log(info, "Check '~s' with fprof profiling~n", [Cmd]), + ok = check(Cmd ++ " -p", should_succeed, + [SharedExpected, "Profiling!", "See fprof\.analysis"], + ["See eflame\.svg"], + FprofFiles, EflameFiles), + delete_files(FprofFiles), + + %% run Cmd with explicitly selected fprof profiling + retest:log(info, "Check '~s' with explicitly selected fprof profiling~n", + [Cmd]), + ok = check(Cmd ++ " -p profiler=fprof", should_succeed, + [SharedExpected, "Profiling!", "See fprof\.analysis"], + ["See eflame\.svg"], + FprofFiles, EflameFiles), + delete_files(FprofFiles), + + case code:lib_dir(eflame) of + {error, bad_name} -> + retest:log(info, + "eflame not found in code path. skip eflame test~n"), + ok; + _EflameDir -> + %% run Cmd with eflame profiling + retest:log(info, "Check '~s' with eflame profiling~n", [Cmd]), + ok = check(Cmd ++ " -p profiler=eflame", should_succeed, + [SharedExpected, "Profiling!", "See eflame\.svg"], + ["See fprof\.analysis"], + EflameFiles, FprofFiles), + delete_files(EflameFiles) + end, + + ok. + +fprof_files() -> + FprofFiles = ["fprof.trace", "fprof.analysis"], + CgrindFiles = ["fprof.cgrind"], + case os:find_executable("erlgrind") of + false -> + retest:log(info, + "erlgrind escript not found. skip fprof.cgrind check~n"), + FprofFiles; + _ErlGrind -> + FprofFiles ++ CgrindFiles + end. + +check(Cmd, FailureMode, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) -> + case {retest:sh(Cmd), FailureMode} of + {{error, _}=Error, should_succeed} -> + retest:log(error, "cmd '~s' failed:~n~p~n", [Cmd, Error]), + Error; + {{ok, CapturedOutput}, should_succeed} -> + JoinedOutput = string:join(CapturedOutput, "\n"), + check1(Cmd, JoinedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles); + {{error, {stopped, {_Rc, CapturedOutput}}}, should_fail} -> + JoinedOutput = string:join(CapturedOutput, "\n"), + check1(Cmd, JoinedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) + end. + +check1(Cmd, CapturedOutput, ExpectedOutput, UnexpectedOutput, + ExpectedFiles, UnexpectedFiles) -> + ReOpts = [{capture, all, list}], + ExMatches = + lists:zf( + fun(Pattern) -> + case re:run(CapturedOutput, Pattern, ReOpts) of + nomatch -> + retest:log(error, + "Expected pattern '~s' missing " + "in the following output:~n" + "=== BEGIN ===~n~s~n=== END ===~n", + [Pattern, CapturedOutput]), + {true, Pattern}; + {match, _} -> + false + end + end, ExpectedOutput), + + UnExMatches = + lists:zf( + fun(Pattern) -> + case re:run(CapturedOutput, Pattern, ReOpts) of + nomatch -> + false; + {match, [Match]} -> + retest:log( + console, + "Unexpected output when running cmd '~s':~n~s~n", + [Cmd, Match]), + {true, Match} + end + end, UnexpectedOutput), + + ExFiles = + lists:zf( + fun(File) -> + case filelib:is_regular(File) of + true -> + false; + false -> + retest:log(error, + "Expected file missing: ~s~n", [File]), + {true, File} + end + end, ExpectedFiles), + + UnExFiles = + lists:zf( + fun(File) -> + case filelib:is_regular(File) of + true -> + retest:log(error, + "Unexpected file found: ~s~n", [File]), + {true, File}; + false -> + false + end + end, UnexpectedFiles), + + case {ExMatches, UnExMatches, ExFiles, UnExFiles} of + {[], [], [], []} -> + ok; + _ -> + error + end. + +delete_files(Files) -> + lists:foreach(fun(File) -> ok = file:delete(File) end, Files). + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/proto_gpb/include/.gitignore rebar-2.6.0/inttest/proto_gpb/include/.gitignore --- rebar-2.0.0/inttest/proto_gpb/include/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/include/.gitignore 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff -Nru rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb.app.src rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb.app.src --- rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,9 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +{application, gpb, + [{description, "Simple mock of gpb, with enough to generate dummy files"}, + {vsn, "1"}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env, []}]}. diff -Nru rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb_compile.erl rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb_compile.erl --- rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb_compile.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb_compile.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,78 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(gpb_compile). +-export([file/2]). + +%% Simulate gpb compiling some proto files, +%% but generate only enough of what's needed for testing -- dummy stuff only. +%% if a bad.proto file is supplied then gpb fails +file(Proto, Opts) -> + ok = case filename:basename(Proto) of + "bad.proto" -> error; + _ -> ok + end, + Prefix = proplists:get_value(module_name_prefix, Opts, ""), + Suffix = proplists:get_value(module_name_suffix, Opts, ""), + ProtoBase = filename:basename(Proto, ".proto"), + ModBase = Prefix ++ ProtoBase ++ Suffix, + ErlDest = filename:join(get_erl_outdir(Opts), ModBase ++ ".erl"), + HrlDest = filename:join(get_hrl_outdir(Opts), ModBase ++ ".hrl"), + ok = file:write_file(ErlDest, erl_text(ModBase)), + ok = file:write_file(HrlDest, hrl_text(ModBase)). + +erl_text(ModBase) -> + io_lib:format( + lines(["-module(~p).", + "-export([encode_msg/1]).", + "-export([decode_msg/2]).", + "", + "encode_msg(some_dummy_msg) -> <<1,2,3>>.", + "", + "decode_msg(<<1,2,3>>, _) -> some_dummy_msg."]), + [list_to_atom(ModBase)]). + +hrl_text(ModBase) -> + io_lib:format( + lines(["-ifndef(~s_hrl).", + "-define(~s_hrl, true).", + "", + "%% some record definitions would normally go here...", + "" + "-endif. %% ~s_hrl"]), + [ModBase, ModBase, ModBase]). + +get_erl_outdir(Opts) -> + proplists:get_value(o_erl, Opts, get_outdir(Opts)). + +get_hrl_outdir(Opts) -> + proplists:get_value(o_hrl, Opts, get_outdir(Opts)). + +get_outdir(Opts) -> + proplists:get_value(o, Opts, "."). + +lines(Lines) -> + lists:flatten([[L, $\n] || L <- Lines]). diff -Nru rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb.erl rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb.erl --- rebar-2.0.0/inttest/proto_gpb/mock/gpb/src/gpb.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/mock/gpb/src/gpb.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(gpb). diff -Nru rebar-2.0.0/inttest/proto_gpb/proto/a/b/test3.proto rebar-2.6.0/inttest/proto_gpb/proto/a/b/test3.proto --- rebar-2.0.0/inttest/proto_gpb/proto/a/b/test3.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto/a/b/test3.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test3; + +service test3 +{ + rpc testRpc3(RPC_INPUT3) returns (RPC_OUTPUT3); +} + +message RPC_INPUT3 +{ + optional string str = 1; +} + +message RPC_OUTPUT3 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto/a/test2.proto rebar-2.6.0/inttest/proto_gpb/proto/a/test2.proto --- rebar-2.0.0/inttest/proto_gpb/proto/a/test2.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto/a/test2.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test2; + +service test2 +{ + rpc testRpc2(RPC_INPUT2) returns (RPC_OUTPUT2); +} + +message RPC_INPUT2 +{ + optional string str = 1; +} + +message RPC_OUTPUT2 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto/c/d/test5.proto rebar-2.6.0/inttest/proto_gpb/proto/c/d/test5.proto --- rebar-2.0.0/inttest/proto_gpb/proto/c/d/test5.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto/c/d/test5.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test5; + +service test5 +{ + rpc testRpc5(RPC_INPUT5) returns (RPC_OUTPUT5); +} + +message RPC_INPUT5 +{ + optional string str = 1; +} + +message RPC_OUTPUT5 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto/c/test4.proto rebar-2.6.0/inttest/proto_gpb/proto/c/test4.proto --- rebar-2.0.0/inttest/proto_gpb/proto/c/test4.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto/c/test4.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test4; + +service test4 +{ + rpc testRpc4(RPC_INPUT4) returns (RPC_OUTPUT4); +} + +message RPC_INPUT4 +{ + optional string str = 1; +} + +message RPC_OUTPUT4 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto/test.proto rebar-2.6.0/inttest/proto_gpb/proto/test.proto --- rebar-2.0.0/inttest/proto_gpb/proto/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto/test.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test; + +service test +{ + rpc testRpc(RPC_INPUT) returns (RPC_OUTPUT); +} + +message RPC_INPUT +{ + optional string str = 1; +} + +message RPC_OUTPUT +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/a/b/test3.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/a/b/test3.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/a/b/test3.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/a/b/test3.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test3; + +service test3 +{ + rpc testRpc3(RPC_INPUT3) returns (RPC_OUTPUT3); +} + +message RPC_INPUT3 +{ + optional string str = 1; +} + +message RPC_OUTPUT3 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/a/test2.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/a/test2.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/a/test2.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/a/test2.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test2; + +service test2 +{ + rpc testRpc2(RPC_INPUT2) returns (RPC_OUTPUT2); +} + +message RPC_INPUT2 +{ + optional string str = 1; +} + +message RPC_OUTPUT2 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/bad.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/bad.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/bad.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/bad.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test; + +service test +{ + rpc testRpc(RPC_INPUT) returns (RPC_OUTPUT); +} + +message RPC_INPUT + // bug introduced intentionally here + optional string str = 1; +} + +message RPC_OUTPUT +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/c/d/test5.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/c/d/test5.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/c/d/test5.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/c/d/test5.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test5; + +service test5 +{ + rpc testRpc5(RPC_INPUT5) returns (RPC_OUTPUT5); +} + +message RPC_INPUT5 +{ + optional string str = 1; +} + +message RPC_OUTPUT5 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/c/test4.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/c/test4.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/c/test4.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/c/test4.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test4; + +service test4 +{ + rpc testRpc4(RPC_INPUT4) returns (RPC_OUTPUT4); +} + +message RPC_INPUT4 +{ + optional string str = 1; +} + +message RPC_OUTPUT4 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto.bad/test.proto rebar-2.6.0/inttest/proto_gpb/proto.bad/test.proto --- rebar-2.0.0/inttest/proto_gpb/proto.bad/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto.bad/test.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test; + +service test +{ + rpc testRpc(RPC_INPUT) returns (RPC_OUTPUT); +} + +message RPC_INPUT +{ + optional string str = 1; +} + +message RPC_OUTPUT +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/proto_gpb_rt.erl rebar-2.6.0/inttest/proto_gpb/proto_gpb_rt.erl --- rebar-2.0.0/inttest/proto_gpb/proto_gpb_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/proto_gpb_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,204 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Luis Rascão (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(proto_gpb_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("kernel/include/file.hrl"). +-include_lib("deps/retest/include/retest.hrl"). + +-define(MODULES, + [foo, + foo_app, + foo_sup]). + +-define(GENERATED_MODULES, + [test_gpb, + test2_gpb, + test3_gpb, + test4_gpb, + test5_gpb]). + +-define(SOURCE_PROTO_FILES, + ["test.proto", + "a/test2.proto", + "a/b/test3.proto", + "c/test4.proto", + "c/d/test5.proto"]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "rebar2.config", "rebar2.config"}, + {copy, "rebar.bad.config", "rebar.bad.config"}, + {copy, "include", "include"}, + {copy, "src", "src"}, + {copy, "proto", "proto"}, + {copy, "proto.bad", "proto.bad"}, + {copy, "mock", "deps"}, + {create, "ebin/foo.app", app(foo, ?MODULES ++ ?GENERATED_MODULES)} + ]. + +run(_Dir) -> + % perform test obtaining the .proto files from src dir + ok = run_from_dir(success_expected, "src", "rebar.config"), + % perform test obtaining the .proto files from proto dir + ok = run_from_dir(success_expected, "proto", "rebar2.config"), + % perform a test where a failure is expected + ok = run_from_dir(fail_expected, "proto.bad", "rebar.bad.config"). + +run_from_dir(fail_expected, _ProtoDir, ConfigFile) -> + %% we expect a failure to happen, however rebar should not crash; + %% We make sure of that by scanning the error. + {error, {stopped, {1, Error}}} = retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " compile", + []), + %% No matches of the string 'EXIT' should occur, these + %% indicate a rebar crash and not a exit with error. + 0 = string:str(lists:flatten(Error), "'EXIT'"), + ok; +run_from_dir(success_expected, ProtoDir, ConfigFile) -> + ?assertMatch({ok, _}, retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " clean", + [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " compile", + [])), + %% Foo includes test_gpb.hrl, + %% So if it compiled, that also means gpb succeeded in + %% generating the test_gpb.hrl file, and also that it generated + %% the .hrl file was generated before foo was compiled. + ok = check_beams_generated(), + + ?DEBUG("Verifying recompilation~n", []), + TestErl = hd(generated_erl_files()), + TestProto = hd(source_proto_files(ProtoDir)), + make_proto_newer_than_erl(TestProto, TestErl), + TestMTime1 = read_mtime(TestErl), + ?assertMatch({ok, _}, retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " compile", + [])), + TestMTime2 = read_mtime(TestErl), + ?assert(TestMTime2 > TestMTime1), + + ?DEBUG("Verifying recompilation with no changes~n", []), + TestMTime3 = read_mtime(TestErl), + ?assertMatch({ok, _}, retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " compile", + [])), + TestMTime4 = read_mtime(TestErl), + ?assert(TestMTime3 =:= TestMTime4), + + ?DEBUG("Verify cleanup~n", []), + ?assertMatch({ok, _}, retest_sh:run("./rebar --config " + ++ ConfigFile + ++ " clean", + [])), + ok = check_files_deleted(), + ok. + +check_beams_generated() -> + check(fun filelib:is_regular/1, + beam_files()). + +check_files_deleted() -> + check(fun file_does_not_exist/1, + beam_files() ++ generated_erl_files() ++ generated_hrl_files()). + +beam_files() -> + add_dir("ebin", add_ext(?MODULES, ".beam")). + +generated_erl_files() -> + add_dir("src", add_ext(?GENERATED_MODULES, ".erl")). + +generated_hrl_files() -> + add_dir("include", add_ext(?GENERATED_MODULES, ".hrl")). + +generated_beam_files() -> + add_dir("ebin", add_ext(?GENERATED_MODULES, ".beam")). + +source_proto_files(ProtoDir) -> + add_dir(ProtoDir, ?SOURCE_PROTO_FILES). + +file_does_not_exist(F) -> + not filelib:is_regular(F). + +add_ext(Modules, Ext) -> + [lists:concat([Module, Ext]) || Module <- Modules]. + +add_dir(Dir, Files) -> + [filename:join(Dir, File) || File <- Files]. + +read_mtime(File) -> + {ok, #file_info{mtime=MTime}} = file:read_file_info(File), + MTime. + + +make_proto_newer_than_erl(Proto, Erl) -> + %% Do this by back-dating the erl file instead of touching the + %% proto file. Do this instead of sleeping for a second to get a + %% reliable test. Sleeping would have been needed sin ce the + %% #file_info{} (used by eg. filelib:last_modified) does not have + %% sub-second resolution (even though most file systems have). + {ok, #file_info{mtime=ProtoMTime}} = file:read_file_info(Proto), + {ok, ErlInfo} = file:read_file_info(Erl), + OlderMTime = update_seconds_to_datetime(ProtoMTime, -2), + OlderErlInfo = ErlInfo#file_info{mtime = OlderMTime}, + ok = file:write_file_info(Erl, OlderErlInfo). + +update_seconds_to_datetime(DT, ToAdd) -> + calendar:gregorian_seconds_to_datetime( + calendar:datetime_to_gregorian_seconds(DT) + ToAdd). + +touch_file(File) -> + ?assertMatch({ok, _}, retest_sh:run("touch " ++ File, [])). + +check(Check, Files) -> + lists:foreach( + fun(F) -> + ?assertMatch({true, _}, {Check(F), F}) + end, + Files). + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib, gpb]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/proto_gpb/rebar2.config rebar-2.6.0/inttest/proto_gpb/rebar2.config --- rebar-2.0.0/inttest/proto_gpb/rebar2.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/rebar2.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,23 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et + +{erl_opts, + [ + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. + +{deps, + [ + %% The dependency below to gpb is needed for "rebar compile" to + %% work, thus for the inttest to work, but the gpb that is actually + %% used in inttest is brought in from the inttest/proto_gpb/mock + %% subdirectory. + {gpb, ".*"} + ]}. + +{proto_opts, [ + {compiler, gpb}, + {src_dirs, ["proto"]} +]}. + +{gpb_opts, [{module_name_suffix, "_gpb"}]}. diff -Nru rebar-2.0.0/inttest/proto_gpb/rebar.bad.config rebar-2.6.0/inttest/proto_gpb/rebar.bad.config --- rebar-2.0.0/inttest/proto_gpb/rebar.bad.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/rebar.bad.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,23 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et + +{erl_opts, + [ + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. + +{deps, + [ + %% The dependency below to gpb is needed for "rebar compile" to + %% work, thus for the inttest to work, but the gpb that is actually + %% used in inttest is brought in from the inttest/proto_gpb/mock + %% subdirectory. + {gpb, ".*"} + ]}. + +{proto_opts, [ + {compiler, gpb}, + {src_dirs, ["proto.bad"]} +]}. + +{gpb_opts, [{module_name_suffix, "_gpb"}]}. diff -Nru rebar-2.0.0/inttest/proto_gpb/rebar.config rebar-2.6.0/inttest/proto_gpb/rebar.config --- rebar-2.0.0/inttest/proto_gpb/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,22 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et + +{erl_opts, + [ + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. + +{deps, + [ + %% The dependency below to gpb is needed for "rebar compile" to + %% work, thus for the inttest to work, but the gpb that is actually + %% used in inttest is brought in from the inttest/proto_gpb/mock + %% subdirectory. + {gpb, ".*"} + ]}. + +{proto_opts, [ + {compiler, gpb} +]}. + +{gpb_opts, [{module_name_suffix, "_gpb"}]}. diff -Nru rebar-2.0.0/inttest/proto_gpb/src/a/b/test3.proto rebar-2.6.0/inttest/proto_gpb/src/a/b/test3.proto --- rebar-2.0.0/inttest/proto_gpb/src/a/b/test3.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/a/b/test3.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test3; + +service test3 +{ + rpc testRpc3(RPC_INPUT3) returns (RPC_OUTPUT3); +} + +message RPC_INPUT3 +{ + optional string str = 1; +} + +message RPC_OUTPUT3 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/src/a/test2.proto rebar-2.6.0/inttest/proto_gpb/src/a/test2.proto --- rebar-2.0.0/inttest/proto_gpb/src/a/test2.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/a/test2.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test2; + +service test2 +{ + rpc testRpc2(RPC_INPUT2) returns (RPC_OUTPUT2); +} + +message RPC_INPUT2 +{ + optional string str = 1; +} + +message RPC_OUTPUT2 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/src/c/d/test5.proto rebar-2.6.0/inttest/proto_gpb/src/c/d/test5.proto --- rebar-2.0.0/inttest/proto_gpb/src/c/d/test5.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/c/d/test5.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test5; + +service test5 +{ + rpc testRpc5(RPC_INPUT5) returns (RPC_OUTPUT5); +} + +message RPC_INPUT5 +{ + optional string str = 1; +} + +message RPC_OUTPUT5 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/src/c/test4.proto rebar-2.6.0/inttest/proto_gpb/src/c/test4.proto --- rebar-2.0.0/inttest/proto_gpb/src/c/test4.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/c/test4.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test4; + +service test4 +{ + rpc testRpc4(RPC_INPUT4) returns (RPC_OUTPUT4); +} + +message RPC_INPUT4 +{ + optional string str = 1; +} + +message RPC_OUTPUT4 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_gpb/src/foo_app.erl rebar-2.6.0/inttest/proto_gpb/src/foo_app.erl --- rebar-2.0.0/inttest/proto_gpb/src/foo_app.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/foo_app.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo_app). + +-behaviour(application). + +-export([start/2, + stop/1]). + +start(_Type, _Args) -> foo_sup:start_link(). + +stop(_State) -> ok. diff -Nru rebar-2.0.0/inttest/proto_gpb/src/foo.erl rebar-2.6.0/inttest/proto_gpb/src/foo.erl --- rebar-2.0.0/inttest/proto_gpb/src/foo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/foo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,39 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo). + +-export([start_link/0, + start_link/1, + init/1, + terminate/2, + handle_info/2, + handle_call/3, + handle_cast/2, + code_change/3]). + +-behavior(gen_server). + +-include("../include/test_gpb.hrl"). +-include("../include/test2_gpb.hrl"). +-include("../include/test3_gpb.hrl"). +-include("../include/test4_gpb.hrl"). +-include("../include/test5_gpb.hrl"). + +-record(state, {node :: node()}). + +start_link() -> start_link(undefined). + +start_link(Args) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []). + +init(_Args) -> {ok, #state{node=node()}}. + +terminate(_Reason, _Data) -> ok. + +handle_info(_Info, State) -> {noreply, State}. + +handle_cast(_Msg, State) -> {noreply, State}. + +handle_call(_Msg, _From, State) -> {reply, ok, State}. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. diff -Nru rebar-2.0.0/inttest/proto_gpb/src/foo_sup.erl rebar-2.6.0/inttest/proto_gpb/src/foo_sup.erl --- rebar-2.0.0/inttest/proto_gpb/src/foo_sup.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/foo_sup.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,15 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo_sup). + +-behavior(supervisor). + +-export([start_link/0, + init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init(_Args) -> + FooChild = {foo,{foo, start_link, []}, permanent, 5000, worker, [foo]}, + {ok,{{one_for_all,1,1}, [FooChild]}}. diff -Nru rebar-2.0.0/inttest/proto_gpb/src/test.proto rebar-2.6.0/inttest/proto_gpb/src/test.proto --- rebar-2.0.0/inttest/proto_gpb/src/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_gpb/src/test.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,19 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +package test; + +service test +{ + rpc testRpc(RPC_INPUT) returns (RPC_OUTPUT); +} + +message RPC_INPUT +{ + optional string str = 1; +} + +message RPC_OUTPUT +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/proto_protobuffs/include/.gitignore rebar-2.6.0/inttest/proto_protobuffs/include/.gitignore --- rebar-2.0.0/inttest/proto_protobuffs/include/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/include/.gitignore 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff -Nru rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.app.src rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.app.src --- rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,10 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +{application, protobuffs, + [{description, + "Simple mock of erlang_protobuffs, with enough to generate dummy files"}, + {vsn, "1"}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env, []}]}. diff -Nru rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs_compile.erl rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs_compile.erl --- rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs_compile.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs_compile.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,68 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(protobuffs_compile). +-export([scan_file/2]). + +%% Simulate protobuffs compiling some proto files, +%% but generate only enough of what's needed for testing -- dummy stuff only. +scan_file(Proto, _Opts) -> + ProtoBase = filename:basename(Proto, ".proto"), + ModBase = ProtoBase ++ "_pb", + BeamDest = filename:join(get_beam_outdir(), ModBase ++ ".beam"), + HrlDest = filename:join(get_hrl_outdir(), ModBase ++ ".hrl"), + ok = file:write_file(BeamDest, beam_text(ModBase)), + ok = file:write_file(HrlDest, hrl_text(ModBase)). + +beam_text(ModBase) -> + Mod = list_to_atom(ModBase), + Forms = [mk_attr(module, Mod)], % just a -module(...). line + {ok, Mod, Bin} = compile:forms(Forms), + Bin. + +mk_attr(AttrName, AttrValue) -> + erl_syntax:revert( + erl_syntax:attribute(erl_syntax:atom(AttrName), + [erl_syntax:abstract(AttrValue)])). + +hrl_text(ModBase) -> + io_lib:format( + lines(["-ifndef(~s_hrl).", + "-define(~s_hrl, true).", + "", + "%% some record definitions would normally go here...", + "" + "-endif. %% ~s_hrl"]), + [ModBase, ModBase, ModBase]). + +get_beam_outdir() -> + ".". + +get_hrl_outdir() -> + ".". + +lines(Lines) -> + lists:flatten([[L, $\n] || L <- Lines]). diff -Nru rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.erl rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.erl --- rebar-2.0.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/mock/protobuffs/src/protobuffs.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et + +-module(protobuffs). diff -Nru rebar-2.0.0/inttest/proto_protobuffs/proto_protobuffs_rt.erl rebar-2.6.0/inttest/proto_protobuffs/proto_protobuffs_rt.erl --- rebar-2.0.0/inttest/proto_protobuffs/proto_protobuffs_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/proto_protobuffs_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,79 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(proto_protobuffs_rt). +-export([files/0, + run/1]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(MODULES, + [foo, + foo_app, + foo_sup, + test_pb]). + +-define(BEAM_FILES, + ["foo.beam", + "foo_app.beam", + "foo_sup.beam", + "test_pb.beam"]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {copy, "include", "include"}, + {copy, "src", "src"}, + {copy, "mock", "deps"}, + {create, "ebin/foo.app", app(foo, ?MODULES)} + ]. + +run(_Dir) -> + ?assertMatch({ok, _}, retest_sh:run("./rebar clean", [])), + ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])), + ok = check_beams_generated(), + ok. + +check_beams_generated() -> + lists:foreach( + fun(F) -> + File = filename:join("ebin", F), + ?assert(filelib:is_regular(File)) + end, + ?BEAM_FILES). + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib, gpb]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/proto_protobuffs/rebar.config rebar-2.6.0/inttest/proto_protobuffs/rebar.config --- rebar-2.0.0/inttest/proto_protobuffs/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,20 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et + +{erl_opts, + [ + {platform_define, "R13|R14", 'NO_CALLBACK_ATTRIBUTE'} + ]}. + +{deps, + [ + %% The dependency below to protobuffs is needed for "rebar compile" to + %% work, thus for the inttest to work, but the protobuffs that is actually + %% used in inttest is brought in from the inttest/proto_protobuffs/mock + %% subdirectory. + {protobuffs, ".*"} + ]}. + +%% The default proto compiler is protobuffs +%% so don't need to specify this +%% {proto_compiler, protobuffs}. diff -Nru rebar-2.0.0/inttest/proto_protobuffs/src/foo_app.erl rebar-2.6.0/inttest/proto_protobuffs/src/foo_app.erl --- rebar-2.0.0/inttest/proto_protobuffs/src/foo_app.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/src/foo_app.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo_app). + +-behaviour(application). + +-export([start/2, + stop/1]). + +start(_Type, _Args) -> foo_sup:start_link(). + +stop(_State) -> ok. diff -Nru rebar-2.0.0/inttest/proto_protobuffs/src/foo.erl rebar-2.6.0/inttest/proto_protobuffs/src/foo.erl --- rebar-2.0.0/inttest/proto_protobuffs/src/foo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/src/foo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,35 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo). + +-export([start_link/0, + start_link/1, + init/1, + terminate/2, + handle_info/2, + handle_call/3, + handle_cast/2, + code_change/3]). + +-behavior(gen_server). + +-include("../include/test_pb.hrl"). + +-record(state, {node :: node()}). + +start_link() -> start_link(undefined). + +start_link(Args) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, Args, []). + +init(_Args) -> {ok, #state{node=node()}}. + +terminate(_Reason, _Data) -> ok. + +handle_info(_Info, State) -> {noreply, State}. + +handle_cast(_Msg, State) -> {noreply, State}. + +handle_call(_Msg, _From, State) -> {reply, ok, State}. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. diff -Nru rebar-2.0.0/inttest/proto_protobuffs/src/foo_sup.erl rebar-2.6.0/inttest/proto_protobuffs/src/foo_sup.erl --- rebar-2.0.0/inttest/proto_protobuffs/src/foo_sup.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/src/foo_sup.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,15 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(foo_sup). + +-behavior(supervisor). + +-export([start_link/0, + init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init(_Args) -> + FooChild = {foo,{foo, start_link, []}, permanent, 5000, worker, [foo]}, + {ok,{{one_for_all,1,1}, [FooChild]}}. diff -Nru rebar-2.0.0/inttest/proto_protobuffs/src/test.proto rebar-2.6.0/inttest/proto_protobuffs/src/test.proto --- rebar-2.0.0/inttest/proto_protobuffs/src/test.proto 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/proto_protobuffs/src/test.proto 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,12 @@ +// -*- c-basic-offset: 4; indent-tabs-mode: nil -*- +// ex: ts=4 sw=4 et + +message m1 +{ + optional string str = 1; +} + +message m2 +{ + optional string str = 1; +} diff -Nru rebar-2.0.0/inttest/require_vsn/rebar.config rebar-2.6.0/inttest/require_vsn/rebar.config --- rebar-2.0.0/inttest/require_vsn/rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/require_vsn/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +{require_erts_vsn, "no_such_erts_vsn-1.2"}. +{require_otp_vsn, "no_such_otp_vsn-1.2"}. +{require_min_otp_vsn, "no_such_min_otp_vsn-1.0"}. diff -Nru rebar-2.0.0/inttest/require_vsn/require_vsn_rt.erl rebar-2.6.0/inttest/require_vsn/require_vsn_rt.erl --- rebar-2.0.0/inttest/require_vsn/require_vsn_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/require_vsn/require_vsn_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,115 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(require_vsn_rt). +-export([files/0, + run/1]). + +files() -> + [ + {copy, "../../rebar", "rebar"}, + {copy, "rebar.config", "rebar.config"}, + {create, "ebin/require_vsn.app", app(require_vsn, [])} + ]. + +run(_Dir) -> + SharedExpected = "==> require_vsn_rt \\(compile\\)", + %% Provoke ABORT due to failed require vsn check. + retest:log(info, "Check require vsn failure~n"), + ok = check_output("./rebar compile", should_fail, + [SharedExpected, "ERROR: "], + ["WARN: "]), + %% Treat version constraints as warnings. + retest:log(info, "Check require vsn success with -k/--keep-going~n"), + ok = check_output("./rebar -k compile", should_succeed, + [SharedExpected, "ERROR: "], + ["WARN: "]), + ok. + +check_output(Cmd, FailureMode, Expected, Unexpected) -> + case {retest:sh(Cmd), FailureMode} of + {{error, _}=Error, should_succeed} -> + retest:log(error, "cmd '~s' failed:~n~p~n", [Cmd, Error]), + Error; + {{ok, Captured}, should_succeed} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected); + {{error, {stopped, {_Rc, Captured}}}, should_fail} -> + Joined = string:join(Captured, "\n"), + check_output1(Cmd, Joined, Expected, Unexpected) + end. + +check_output1(Cmd, Captured, Expected, Unexpected) -> + ReOpts = [{capture, all, list}], + ExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + retest:log(error, + "Expected pattern '~s' missing " + "in the following output:~n" + "=== BEGIN ===~n~s~n=== END ===~n", + [Pattern, Captured]), + {true, Pattern}; + {match, _} -> + false + end + end, Expected), + + UnExMatches = + lists:zf( + fun(Pattern) -> + case re:run(Captured, Pattern, ReOpts) of + nomatch -> + false; + {match, [Match]} -> + retest:log( + console, + "Unexpected output when running cmd '~s':~n~s~n", + [Cmd, Match]), + {true, Match} + end + end, Unexpected), + + case {ExMatches, UnExMatches} of + {[], []} -> + ok; + _ -> + error + end. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). Binary files /tmp/tmpDfMbnv/3RQpdXjcS8/rebar-2.0.0/inttest/retest and /tmp/tmpDfMbnv/2bJx_Hzbkj/rebar-2.6.0/inttest/retest differ diff -Nru rebar-2.0.0/inttest/rgen1/retest.config rebar-2.6.0/inttest/rgen1/retest.config --- rebar-2.0.0/inttest/rgen1/retest.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/rgen1/retest.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{timeout, 120000}. diff -Nru rebar-2.0.0/inttest/rgen1/rgen1_rt.erl rebar-2.6.0/inttest/rgen1/rgen1_rt.erl --- rebar-2.0.0/inttest/rgen1/rgen1_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/rgen1/rgen1_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(rgen1_rt). -compile(export_all). diff -Nru rebar-2.0.0/inttest/t_custom_config/custom.config rebar-2.6.0/inttest/t_custom_config/custom.config --- rebar-2.0.0/inttest/t_custom_config/custom.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/t_custom_config/custom.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,4 +1,3 @@ - {deps, [ {boo, "."} ]}. diff -Nru rebar-2.0.0/inttest/t_custom_config/t_custom_config_rt.erl rebar-2.6.0/inttest/t_custom_config/t_custom_config_rt.erl --- rebar-2.0.0/inttest/t_custom_config/t_custom_config_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/t_custom_config/t_custom_config_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(t_custom_config_rt). -compile(export_all). @@ -11,16 +13,17 @@ 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 -vv", + [{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 -Nru rebar-2.0.0/inttest/tdeps1/a.erl rebar-2.6.0/inttest/tdeps1/a.erl --- rebar-2.0.0/inttest/tdeps1/a.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/a.erl 2015-06-19 16:14:28.000000000 +0000 @@ -6,4 +6,3 @@ hello() -> io:format("~s\n", [?HELLO]). - diff -Nru rebar-2.0.0/inttest/tdeps1/a.rebar.config rebar-2.6.0/inttest/tdeps1/a.rebar.config --- rebar-2.0.0/inttest/tdeps1/a.rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/a.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}. - +{deps, [{b, "1", {git, "../repo/b"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps1/b.hrl rebar-2.6.0/inttest/tdeps1/b.hrl --- rebar-2.0.0/inttest/tdeps1/b.hrl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/b.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1 @@ - -include_lib("c/include/c.hrl"). - diff -Nru rebar-2.0.0/inttest/tdeps1/b.rebar.config rebar-2.6.0/inttest/tdeps1/b.rebar.config --- rebar-2.0.0/inttest/tdeps1/b.rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/b.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}. - +{deps, [{c, "1", {git, "../repo/c"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps1/c.hrl rebar-2.6.0/inttest/tdeps1/c.hrl --- rebar-2.0.0/inttest/tdeps1/c.hrl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/c.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -define(HELLO, hello). - diff -Nru rebar-2.0.0/inttest/tdeps1/tdeps1_rt.erl rebar-2.6.0/inttest/tdeps1/tdeps1_rt.erl --- rebar-2.0.0/inttest/tdeps1/tdeps1_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps1/tdeps1_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(tdeps1_rt). -compile(export_all). @@ -23,21 +25,30 @@ {copy, "c.hrl", "repo/c/include/c.hrl"} ]. +apply_cmds([], _Params) -> + ok; +apply_cmds([Cmd | Rest], Params) -> + io:format("Running: ~s (~p)\n", [Cmd, Params]), + {ok, _} = retest_sh:run(Cmd, Params), + apply_cmds(Rest, Params). + run(_Dir) -> - %% Initialize the b/c apps as mercurial repos so that dependencies pull + %% Initialize the b/c apps as git repos so that dependencies pull %% properly - HgCmd = "/bin/sh -c \"hg init && hg add && hg commit -m 'Initial commit'\"", - {ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]), - {ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]), - + GitCmds = ["git init", + "git add -A", + "git config user.email 'tdeps@example.com'", + "git config user.name 'tdeps'", + "git commit -a -m 'Initial Commit'"], + apply_cmds(GitCmds, [{dir, "repo/b"}]), + apply_cmds(GitCmds, [{dir, "repo/c"}]), - {ok, _} = retest_sh:run("./rebar get-deps compile", []), + {ok, _} = retest_sh:run("./rebar get-deps", []), + {ok, _} = retest_sh:run("./rebar compile", []), true = filelib:is_regular("ebin/a.beam"), ok. - - %% %% Generate the contents of a simple .app file %% diff -Nru rebar-2.0.0/inttest/tdeps2/a.erl rebar-2.6.0/inttest/tdeps2/a.erl --- rebar-2.0.0/inttest/tdeps2/a.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/a.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,4 +1,3 @@ -module({{module}}). -include_lib("b/include/b.hrl"). - diff -Nru rebar-2.0.0/inttest/tdeps2/a.rebar.config rebar-2.6.0/inttest/tdeps2/a.rebar.config --- rebar-2.0.0/inttest/tdeps2/a.rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/a.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}. - +{deps, [{b, "1", {git, "../repo/b"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps2/b.hrl rebar-2.6.0/inttest/tdeps2/b.hrl --- rebar-2.0.0/inttest/tdeps2/b.hrl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/b.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1 @@ - -include_lib("c/include/c.hrl"). - diff -Nru rebar-2.0.0/inttest/tdeps2/b.rebar.config rebar-2.6.0/inttest/tdeps2/b.rebar.config --- rebar-2.0.0/inttest/tdeps2/b.rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/b.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}. - +{deps, [{c, "1", {git, "../repo/c"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps2/c.hrl rebar-2.6.0/inttest/tdeps2/c.hrl --- rebar-2.0.0/inttest/tdeps2/c.hrl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/c.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -1,2 +1 @@ -define(HELLO, hello). - diff -Nru rebar-2.0.0/inttest/tdeps2/tdeps2_rt.erl rebar-2.6.0/inttest/tdeps2/tdeps2_rt.erl --- rebar-2.0.0/inttest/tdeps2/tdeps2_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps2/tdeps2_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(tdeps2_rt). -compile(export_all). @@ -31,19 +33,28 @@ {copy, "c.hrl", "repo/c/include/c.hrl"} ]. +apply_cmds([], _Params) -> + ok; +apply_cmds([Cmd | Rest], Params) -> + io:format("Running: ~s (~p)\n", [Cmd, Params]), + {ok, _} = retest_sh:run(Cmd, Params), + apply_cmds(Rest, Params). + run(_Dir) -> - %% Initialize the b/c apps as mercurial repos so that dependencies pull + %% Initialize the b/c apps as git repos so that dependencies pull %% properly - HgCmd = "/bin/sh -c \"hg init && hg add && hg commit -m 'Initial commit'\"", - {ok, _} = retest_sh:run(HgCmd, [{dir, "repo/b"}]), - {ok, _} = retest_sh:run(HgCmd, [{dir, "repo/c"}]), - + GitCmds = ["git init", + "git add -A", + "git config user.email 'tdeps@example.com'", + "git config user.name 'tdeps'", + "git commit -a -m 'Initial Commit'"], + ok = apply_cmds(GitCmds, [{dir, "repo/b"}]), + ok = apply_cmds(GitCmds, [{dir, "repo/c"}]), - {ok, _} = retest_sh:run("./rebar -v get-deps compile", []), + {ok, _} = retest_sh:run("./rebar -v get-deps", []), + {ok, _} = retest_sh:run("./rebar -v compile", []), ok. - - %% %% Generate the contents of a simple .app file %% diff -Nru rebar-2.0.0/inttest/tdeps3/a.erl rebar-2.6.0/inttest/tdeps3/a.erl --- rebar-2.0.0/inttest/tdeps3/a.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/a.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +-module({{module}}). + +-include_lib("{{dep}}/include/{{dep}}.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps3/a.rebar.config rebar-2.6.0/inttest/tdeps3/a.rebar.config --- rebar-2.0.0/inttest/tdeps3/a.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/a.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +{deps, [ + {b, "1", {git, "../repo/b"}}, + {f, "1", {git, "../repo/f"}} +]}. diff -Nru rebar-2.0.0/inttest/tdeps3/b.hrl rebar-2.6.0/inttest/tdeps3/b.hrl --- rebar-2.0.0/inttest/tdeps3/b.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/b.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("c/include/c.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps3/b.rebar.config rebar-2.6.0/inttest/tdeps3/b.rebar.config --- rebar-2.0.0/inttest/tdeps3/b.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/b.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,5 @@ +{deps, [ + {c, "1", {git, "../repo/c"}} +]}. + +{lib_dirs, [apps]}. diff -Nru rebar-2.0.0/inttest/tdeps3/c.hrl rebar-2.6.0/inttest/tdeps3/c.hrl --- rebar-2.0.0/inttest/tdeps3/c.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/c.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("d/include/d.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps3/c.rebar.config rebar-2.6.0/inttest/tdeps3/c.rebar.config --- rebar-2.0.0/inttest/tdeps3/c.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/c.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{d, "1", {git, "../repo/d"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps3/d.hrl rebar-2.6.0/inttest/tdeps3/d.hrl --- rebar-2.0.0/inttest/tdeps3/d.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/d.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("e/include/e.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps3/d.rebar.config rebar-2.6.0/inttest/tdeps3/d.rebar.config --- rebar-2.0.0/inttest/tdeps3/d.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/d.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{e, "1", {git, "../repo/e"}}]}. diff -Nru rebar-2.0.0/inttest/tdeps3/e.hrl rebar-2.6.0/inttest/tdeps3/e.hrl --- rebar-2.0.0/inttest/tdeps3/e.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/e.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-define(HELLO, hello). diff -Nru rebar-2.0.0/inttest/tdeps3/f.hrl rebar-2.6.0/inttest/tdeps3/f.hrl --- rebar-2.0.0/inttest/tdeps3/f.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/f.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("e/include/e.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps3/root.rebar.config rebar-2.6.0/inttest/tdeps3/root.rebar.config --- rebar-2.0.0/inttest/tdeps3/root.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/root.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{sub_dirs, ["apps/a"]}. diff -Nru rebar-2.0.0/inttest/tdeps3/tdeps3_rt.erl rebar-2.6.0/inttest/tdeps3/tdeps3_rt.erl --- rebar-2.0.0/inttest/tdeps3/tdeps3_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps3/tdeps3_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,91 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(tdeps3_rt). + +-compile(export_all). + +%% Exercise transitive dependencies where there are multiple files +%% depending on the same set of deps as well as lib_dir directives +%% A -> B -> C -> D -> E +%% |--> G(via lib_dir) +%% |--> F -> D -> E + +files() -> + [ + %% A1 application + {create, "ebin/a.app", app(a, [a])}, + {template, "a.erl", "src/a.erl", dict:from_list([{module, a}, {dep, b}])}, + + {copy, "a.rebar.config", "rebar.config"}, + {copy, "../../rebar", "rebar"}, + + %% B application + {create, "repo/b/ebin/b.app", app(b, [b])}, + {template, "a.erl", "repo/b/src/b.erl", dict:from_list([{module, b}, {dep, b}])}, + {copy, "b.rebar.config", "repo/b/rebar.config"}, + {copy, "b.hrl", "repo/b/include/b.hrl"}, + + %% C application + {create, "repo/c/ebin/c.app", app(c, [c])}, + {template, "a.erl", "repo/c/src/c.erl", dict:from_list([{module, c}, {dep, d}])}, + {copy, "c.rebar.config", "repo/c/rebar.config"}, + {copy, "c.hrl", "repo/c/include/c.hrl"}, + + %% D application + {create, "repo/d/ebin/d.app", app(d, [d])}, + {template, "a.erl", "repo/d/src/d.erl", dict:from_list([{module, d}, {dep, e}])}, + {copy, "d.rebar.config", "repo/d/rebar.config"}, + {copy, "d.hrl", "repo/d/include/d.hrl"}, + + %% E application + {create, "repo/e/ebin/e.app", app(e, [])}, + {copy, "e.hrl", "repo/e/include/e.hrl"}, + + + %% F application + {create, "repo/f/ebin/f.app", app(f, [f])}, + {template, "a.erl", "repo/f/src/f.erl", dict:from_list([{module, f}, {dep, d}])}, + {copy, "c.rebar.config", "repo/f/rebar.config"}, + {copy, "f.hrl", "repo/f/include/f.hrl"}, + + %% G application, which is part of the B repo, in a lib_dir + {create, "repo/b/apps/g/ebin/g.app", app(g, [])}, + {copy, "e.hrl", "repo/b/apps/g/include/g.hrl"} + + ]. + +apply_cmds([], _Params) -> + ok; +apply_cmds([Cmd | Rest], Params) -> + io:format("Running: ~s (~p)\n", [Cmd, Params]), + {ok, _} = retest_sh:run(Cmd, Params), + apply_cmds(Rest, Params). + +run(_Dir) -> + %% Initialize the b/c apps as git repos so that dependencies pull + %% properly + GitCmds = ["git init", + "git add -A", + "git config user.email 'tdeps@example.com'", + "git config user.name 'tdeps'", + "git commit -a -m 'Initial Commit'"], + ok = apply_cmds(GitCmds, [{dir, "repo/b"}]), + ok = apply_cmds(GitCmds, [{dir, "repo/c"}]), + ok = apply_cmds(GitCmds, [{dir, "repo/d"}]), + ok = apply_cmds(GitCmds, [{dir, "repo/e"}]), + ok = apply_cmds(GitCmds, [{dir, "repo/f"}]), + + {ok, _} = retest_sh:run("./rebar -v get-deps compile", []), + ok. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, "1"}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/tdeps_update/a2.rebar.config rebar-2.6.0/inttest/tdeps_update/a2.rebar.config --- rebar-2.0.0/inttest/tdeps_update/a2.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/a2.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{b, "0.2.4", {git, "../repo/b", {tag, "0.2.4"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/a3.rebar.config rebar-2.6.0/inttest/tdeps_update/a3.rebar.config --- rebar-2.0.0/inttest/tdeps_update/a3.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/a3.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{b, "0.2.5", {git, "../repo/b", {tag, "0.2.5"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/a4.rebar.config rebar-2.6.0/inttest/tdeps_update/a4.rebar.config --- rebar-2.0.0/inttest/tdeps_update/a4.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/a4.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +{deps, [ + {b, "0.2.6", {git, "../repo/b", {tag, "0.2.6"}}}, + {f, "0.1", {git, "../repo/f", {tag, "0.1"}}} + ]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/a.erl rebar-2.6.0/inttest/tdeps_update/a.erl --- rebar-2.0.0/inttest/tdeps_update/a.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/a.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +-module({{module}}). + +-include_lib("b/include/b.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps_update/a.rebar.config rebar-2.6.0/inttest/tdeps_update/a.rebar.config --- rebar-2.0.0/inttest/tdeps_update/a.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/a.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{b, "0.2.3", {git, "../repo/b", {tag, "0.2.3"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/b2.rebar.config rebar-2.6.0/inttest/tdeps_update/b2.rebar.config --- rebar-2.0.0/inttest/tdeps_update/b2.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/b2.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{c, "1.1", {git, "../repo/c", {tag, "1.1"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/b3.rebar.config rebar-2.6.0/inttest/tdeps_update/b3.rebar.config --- rebar-2.0.0/inttest/tdeps_update/b3.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/b3.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{c, "1.2", {git, "../repo/c", {tag, "1.2"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/b4.rebar.config rebar-2.6.0/inttest/tdeps_update/b4.rebar.config --- rebar-2.0.0/inttest/tdeps_update/b4.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/b4.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{c, "1.3", {git, "../repo/c", {tag, "1.3"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/b.hrl rebar-2.6.0/inttest/tdeps_update/b.hrl --- rebar-2.0.0/inttest/tdeps_update/b.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/b.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("c/include/c.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps_update/b.rebar.config rebar-2.6.0/inttest/tdeps_update/b.rebar.config --- rebar-2.0.0/inttest/tdeps_update/b.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/b.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{c, "1.0", {git, "../repo/c", {tag, "1.0"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/c2.hrl rebar-2.6.0/inttest/tdeps_update/c2.hrl --- rebar-2.0.0/inttest/tdeps_update/c2.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/c2.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-include_lib("d/include/d.hrl"). diff -Nru rebar-2.0.0/inttest/tdeps_update/c2.rebar.config rebar-2.6.0/inttest/tdeps_update/c2.rebar.config --- rebar-2.0.0/inttest/tdeps_update/c2.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/c2.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +{deps, [ + {d, "0.7", {git, "../repo/d", {tag, "0.7"}}}, + {e, "2.0", {git, "../repo/e", {tag, "2.0"}}} + ]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/c3.rebar.config rebar-2.6.0/inttest/tdeps_update/c3.rebar.config --- rebar-2.0.0/inttest/tdeps_update/c3.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/c3.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,4 @@ +{deps, [ + {d, "0.7", {git, "../repo/d", {tag, "0.7"}}}, + {e, "2.1", {git, "../repo/e", {tag, "2.1"}}} + ]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/c.hrl rebar-2.6.0/inttest/tdeps_update/c.hrl --- rebar-2.0.0/inttest/tdeps_update/c.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/c.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-define(HELLO, hello). diff -Nru rebar-2.0.0/inttest/tdeps_update/c.rebar.config rebar-2.6.0/inttest/tdeps_update/c.rebar.config --- rebar-2.0.0/inttest/tdeps_update/c.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/c.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{deps, [{d, "0.7", {git, "../repo/d", {tag, "0.7"}}}]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/d.hrl rebar-2.6.0/inttest/tdeps_update/d.hrl --- rebar-2.0.0/inttest/tdeps_update/d.hrl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/d.hrl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +-define(HELLO, hello). diff -Nru rebar-2.0.0/inttest/tdeps_update/root.rebar.config rebar-2.6.0/inttest/tdeps_update/root.rebar.config --- rebar-2.0.0/inttest/tdeps_update/root.rebar.config 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/root.rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1 @@ +{sub_dirs, ["apps/a1"]}. diff -Nru rebar-2.0.0/inttest/tdeps_update/tdeps_update_rt.erl rebar-2.6.0/inttest/tdeps_update/tdeps_update_rt.erl --- rebar-2.0.0/inttest/tdeps_update/tdeps_update_rt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/inttest/tdeps_update/tdeps_update_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,150 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(tdeps_update_rt). + +-compile(export_all). + +%% Exercises update deps, with recursive dependency updates. +%% Initially: +%% A(v0.5) -> B(v0.2.3) -> C(v1.0) +%% But after updating A to 0.6: +%% A(v0.6) -> B(v0.2.4) -> C(v1.1) +%% -> D(v0.7) +%% And after updating A to 0.7: +%% A(v0.7) -> B(v0.2.5) -> C(v1.2) -> E(v2.0) +%% -> D(v0.7) +%% And after updating A to 0.8: +%% A(v0.8) -> B(v0.2.6) -> C(v1.3) -> E(v2.1) +%% -> D(v0.7) +%% -> F(v0.1) -> E(v2.1) +files() -> + [ + %% A1 application + {create, "apps/a1/ebin/a1.app", app(a1, [a1], "0.5")}, + {copy, "a.rebar.config", "apps/a1/rebar.config"}, + {template, "a.erl", "apps/a1/src/a1.erl", dict:from_list([{module, a1}])}, + + {copy, "root.rebar.config", "rebar.config"}, + {copy, "../../rebar", "rebar"}, + + %% B application + {create, "repo/b/ebin/b.app", app(b, [], "0.2.3")}, + {create, "b2.app", app(b, [], "0.2.4")}, + {create, "b3.app", app(b, [], "0.2.5")}, + {create, "b4.app", app(b, [], "0.2.6")}, + {copy, "b.rebar.config", "repo/b/rebar.config"}, + {copy, "b.hrl", "repo/b/include/b.hrl"}, + + %% C application + {create, "repo/c/ebin/c.app", app(c, [], "1.0")}, + {create, "c2.app", app(c, [], "1.1")}, + {create, "c3.app", app(c, [], "1.2")}, + {create, "c4.app", app(c, [], "1.3")}, + {copy, "c.hrl", "repo/c/include/c.hrl"}, + + %% D application + {create, "repo/d/ebin/d.app", app(d, [], "0.7")}, + {copy, "d.hrl", "repo/d/include/d.hrl"}, + + %% E application + {create, "repo/e/ebin/e.app", app(e, [], "2.0")}, + {create, "e2.app", app(e, [], "2.1")}, + + %% F application + {create, "repo/f/ebin/f.app", app(f, [], "0.1")}, + + %% update files + {copy, "a2.rebar.config", "a2.rebar.config"}, + {copy, "a3.rebar.config", "a3.rebar.config"}, + {copy, "a4.rebar.config", "a4.rebar.config"}, + {copy, "b2.rebar.config", "b2.rebar.config"}, + {copy, "b3.rebar.config", "b3.rebar.config"}, + {copy, "b4.rebar.config", "b4.rebar.config"}, + {copy, "c2.hrl", "c2.hrl"}, + {copy, "c.rebar.config", "c.rebar.config"}, + {copy, "c2.rebar.config", "c2.rebar.config"}, + {copy, "c3.rebar.config", "c3.rebar.config"} + ]. + +apply_cmds([], _Params) -> + ok; +apply_cmds([Cmd | Rest], Params) -> + io:format("Running: ~s (~p)\n", [Cmd, Params]), + {ok, _} = retest_sh:run(Cmd, Params), + apply_cmds(Rest, Params). + +run(_Dir) -> + %% Initialize the b/c/d apps as git repos so that dependencies pull + %% properly + GitCmds = ["git init", + "git add -A", + "git config user.email 'tdeps@example.com'", + "git config user.name 'tdeps'", + "git commit -a -m 'Initial Commit'"], + BCmds = ["git tag 0.2.3", + "cp ../../b2.rebar.config rebar.config", + "cp ../../b2.app ebin/b.app", + "git commit -a -m 'update to 0.2.4'", + "git tag 0.2.4", + "cp ../../b3.rebar.config rebar.config", + "cp ../../b3.app ebin/b.app", + "git commit -a -m 'update to 0.2.5'", + "git tag 0.2.5", + "cp ../../b4.rebar.config rebar.config", + "cp ../../b4.app ebin/b.app", + "git commit -a -m 'update to 0.2.6'", + "git tag 0.2.6"], + %"git checkout 0.2.3"], + CCmds = ["git tag 1.0", + "cp ../../c2.hrl include/c.hrl", + "cp ../../c2.app ebin/c.app", + "cp ../../c.rebar.config rebar.config", + "git add rebar.config", + "git commit -a -m 'update to 1.1'", + "git tag 1.1", + "cp ../../c3.app ebin/c.app", + "cp ../../c2.rebar.config rebar.config", + "git commit -a -m 'update to 1.2'", + "git tag 1.2", + "cp ../../c4.app ebin/c.app", + "cp ../../c3.rebar.config rebar.config", + "git commit -a -m 'update to 1.3'", + "git tag 1.3"], + %"git checkout 1.0"], + DCmds = ["git tag 0.7"], + ECmds = ["git tag 2.0", + "cp ../../e2.app ebin/e.app", + "git commit -a -m 'update to 2.1'", + "git tag 2.1"], + FCmds = ["git tag 0.1"], + + ok = apply_cmds(GitCmds++BCmds, [{dir, "repo/b"}]), + ok = apply_cmds(GitCmds++CCmds, [{dir, "repo/c"}]), + ok = apply_cmds(GitCmds++DCmds, [{dir, "repo/d"}]), + ok = apply_cmds(GitCmds++ECmds, [{dir, "repo/e"}]), + ok = apply_cmds(GitCmds++FCmds, [{dir, "repo/f"}]), + + {ok, _} = retest_sh:run("./rebar -v get-deps", []), + {ok, _} = retest_sh:run("./rebar -v compile", []), + os:cmd("cp a2.rebar.config apps/a1/rebar.config"), + {ok, _} = retest_sh:run("./rebar -v update-deps", []), + {ok, _} = retest_sh:run("./rebar -v compile", []), + os:cmd("cp a3.rebar.config apps/a1/rebar.config"), + {ok, _} = retest_sh:run("./rebar -v update-deps", []), + {ok, _} = retest_sh:run("./rebar -v compile", []), + os:cmd("cp a4.rebar.config apps/a1/rebar.config"), + {ok, _} = retest_sh:run("./rebar -v update-deps", []), + {ok, _} = retest_sh:run("./rebar -v compile", []), + ok. + +%% +%% Generate the contents of a simple .app file +%% +app(Name, Modules, Version) -> + App = {application, Name, + [{description, atom_to_list(Name)}, + {vsn, Version}, + {modules, Modules}, + {registered, []}, + {applications, [kernel, stdlib]}]}, + io_lib:format("~p.\n", [App]). diff -Nru rebar-2.0.0/inttest/thooks/thooks_rt.erl rebar-2.6.0/inttest/thooks/thooks_rt.erl --- rebar-2.0.0/inttest/thooks/thooks_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/thooks/thooks_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(thooks_rt). -include_lib("eunit/include/eunit.hrl"). diff -Nru rebar-2.0.0/inttest/tplugins/tplugins_rt.erl rebar-2.6.0/inttest/tplugins/tplugins_rt.erl --- rebar-2.0.0/inttest/tplugins/tplugins_rt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/inttest/tplugins/tplugins_rt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et -module(tplugins_rt). -compile(export_all). @@ -18,7 +20,7 @@ {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 -Nru rebar-2.0.0/Makefile rebar-2.6.0/Makefile --- rebar-2.0.0/Makefile 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/Makefile 2015-06-19 16:14:28.000000000 +0000 @@ -1,27 +1,48 @@ -.PHONY: dialyzer_warnings xref_warnings +.PHONY: clean xref_warnings deps test test_eunit test_inttest + +REBAR=$(PWD)/rebar +RETEST=$(PWD)/deps/retest/retest +LOG_LEVEL?=debug +RT_TARGETS?=inttest all: ./bootstrap clean: - @rm -rf rebar ebin/*.beam inttest/rt.work + @rm -rf rebar .rebar/erlcinfo ebin/*.beam inttest/rt.work rt.work .eunit + +distclean: clean + @rm -rf deps debug: @./bootstrap debug -check: debug xref dialyzer +check: debug xref dialyzer deps test xref: @./rebar xref -dialyzer: dialyzer_warnings - @diff -U0 dialyzer_reference dialyzer_warnings +build_plt: + @./rebar build-plt -dialyzer_warnings: - -@dialyzer -q -n ebin -Wunmatched_returns -Werror_handling \ - -Wrace_conditions > dialyzer_warnings +dialyzer: + @./rebar dialyze 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: test_eunit test_inttest + +test_eunit: all + @$(REBAR) eunit + +test_inttest: all deps + @$(RETEST) -l $(LOG_LEVEL) $(RT_TARGETS) + +travis: clean debug xref clean all deps test diff -Nru rebar-2.0.0/NOTES.org rebar-2.6.0/NOTES.org --- rebar-2.0.0/NOTES.org 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/NOTES.org 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ - -* Major operations -** Compile -*** Code generation -*** Compilation/linking -*** App validation -** Clean -** ct testing -** eunit testing -** Installation -** Doc generation - -* Modes/File types -** Erlang -** Port driver -** NIF driver -** SNMP MIBs -** ASN.1 files - -* Misc. Notes -** Port/NIF driver compilation needs pre/post hook -** Need to support code generation for things like protobuf -** Need to support compilation flags - -* Contexts -** Application -** General -** Release ?! diff -Nru rebar-2.0.0/pr2relnotes.py rebar-2.6.0/pr2relnotes.py --- rebar-2.0.0/pr2relnotes.py 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/pr2relnotes.py 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,188 @@ +#!/usr/bin/env python + +## Install info +## $ virtualenv env +## $ source env/bin/activate +## $ pip install PyGithub +## +## Examples: +## Find the differences from last tag to current +## $ pr2relnotes.py alpha-6 HEAD + +import argparse +import re +import os +import subprocess +from github import Github +from github import GithubException + + +def dprint(*args): + if VERBOSE: + print str(args) + +def get_args(): + """ + Get command line arguments + """ + parser = argparse.ArgumentParser(description="Find the PR's between two versions") + parser.add_argument("old", help = "old version to use") + parser.add_argument("new", help = "new version to use") + parser.add_argument("-v", "--verbose", help="Enable debug output", + default=False, + action="store_true") + parser.add_argument("-f", "--file", + help="Output file to store results (default: tagdiff.md)", + default="tagdiff.md") + return parser.parse_args() + +def search_prs(log): + """ + Search lines of text for PR numbers + """ + # Find all matches using regex iterator, using the PR # as the group match + resultlist = [str(m.group(1)) for m in re.finditer(r"erge pull request #(\d+)", log)] + return sorted(resultlist) + +def get_env(env): + return os.environ[env] + +def get_formatted_issue(repo, issue, title, url): + """ + Single place to adjust formatting output of PR data + """ + # Newline support writelines() call which doesn't add newlines + # on its own + return("* {}/{}: [{}]({})\n".format(repo, issue, title, url)) + +def gh_get_issue_output(org, repo, issuenum): + """ + Look up PR information using the GitHub api + """ + # Attempt to look up the PR, and don't take down the whole + # shebang if a API call fails + # This will fail often on forks who don't have the + # PRs numbers associated with the forked account + # Return empty string on error + try: + repoObj = gh.get_repo(org + "/" + repo) + issue = repoObj.get_issue(int(issuenum)) + title = issue.title + html_url = issue.html_url + except GithubException as e: + print "Github error({0}): {1}".format(e.status, e.data) + return "" + except: + print "Some github error" + return "" + + return(get_formatted_issue(repo, issuenum, title, html_url)) + + +def get_org(repourl): + """ + Simple function to parse the organization out of a GitHub URL + """ + dprint("Current repourl to search: " + repourl) + # GitHub URLs can be: + # http[s]://www.github.com/org/repo + # or git@github.com:/org/repo + pattern = re.compile(r"github.com[/:]+(\w+)/") + m = re.search(pattern, repourl) + # Fail fast if this is wrong so we can add a pattern to the search + if m: + return m.group(1) + else: + raise Exception("Incorrect regex pattern finding repo org") + +def get_name(repourl): + """ + Simple function to parse the repository name out of a GitHub URL + """ + dprint("Current repourl to search: " + repourl) + repo_pattern = re.compile(r"github.com[/:]\w+/(\w+)") + m = re.search(repo_pattern, repourl) + if m: + return m.group(1) + else: + raise Exception("Incorrect rexex pattern finding repo url") + +def get_repo_url_from_remote(): + """ + Function that gets the repository URL from the `git remote` listing + """ + git_remote_bytes = subprocess.check_output(["git", "remote", "-v"]) + # check_output returns the command results in raw byte format + remote_string = git_remote_bytes.decode('utf-8') + + pattern = re.compile(r"github.com[/:]\w+/\w+") + m = re.search(pattern, remote_string) + if m: + return m.group(0) + else: + raise Exception("Incorrect rexex pattern finding repo url") + +def process_log(gitlog, repo_url): + """ + Handles the processing of the gitlog and returns a list + of PRs already formatted for output + """ + pr_list = search_prs(gitlog) + repoorg = get_org(repo_url) + reponame = get_name(repo_url) + pr_buffer = [] + for issue in pr_list: + pr_buffer.append(gh_get_issue_output(repoorg, reponame, issue)) + + return pr_buffer + +def fetch_log(old_ver, new_ver): + """ + Function that processes the git log between the old and new versions + """ + dprint("Current working directory", os.getcwd()) + gitlogbytes = subprocess.check_output(["git", "log", + str(old_ver + ".." + new_ver)]) + return gitlogbytes.decode('utf-8') + + +def compare_versions(repo_url, old_ver, new_ver): + # Formatted list of all PRs for all repos + pr_out = [] + gitlog = fetch_log(old_ver, new_ver) + pr_out.extend(process_log(gitlog, repo_url)) + return pr_out + +def main(): + args = get_args() + + # Setup the GitHub object for later use + global gh + gh = Github(get_env("GHAUTH")) + + if gh == "": + raise Exception("Env var GHAUTH must be set to a valid GitHub API key") + + if args.verbose: + global VERBOSE + VERBOSE=True + + dprint("Inspecting difference in between: ", args.old, " and ", args.new) + + # Find the github URL of the repo we are operating on + repo_url = get_repo_url_from_remote() + + # Compare old and new versions + pr_list = compare_versions(repo_url, args.old, args.new) + + # Writeout PR listing + print "Writing output to file %s" % args.file + with open(args.file, 'w') as output: + output.writelines(pr_list) + + +if __name__ == "__main__": + VERBOSE=False + gh=None + topdir=os.getcwd() + main() diff -Nru rebar-2.0.0/priv/shell-completion/bash/rebar rebar-2.6.0/priv/shell-completion/bash/rebar --- rebar-2.0.0/priv/shell-completion/bash/rebar 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/shell-completion/bash/rebar 2015-06-19 16:14:28.000000000 +0000 @@ -6,13 +6,67 @@ COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - sopts="-h -c -v -V -f -j" - 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=" + sopts="-h -c -v -V -f -D -j -C -p -k -r" + lopts="--help \ + --commands \ + --verbose \ + --force \ + --jobs \ + --config \ + --profile \ + --keep-going \ + --recursive \ + --version" + cmdsnvars=" \ + build-plt \ + check-deps \ + clean \ + compile \ + check-plt \ + create \ + create-app \ + create-lib \ + create-node \ + ct \ + dialyze \ + doc \ + delete-deps \ + escriptize \ + eunit \ + get-deps \ + generate \ + generate-appups \ + generate-upgrade \ + help \ + list-deps \ + list-templates \ + prepare-deps \ + qc \ + refresh-deps \ + shell \ + update-deps \ + version \ + xref \ + overlay \ + apps= \ + case= \ + dump_spec=1 \ + force=1 \ + jobs= \ + suites= \ + verbose=1 \ + appid= \ + overlay_vars= \ + previous_release= \ + profiler= \ + nodeid= \ + root_dir= \ + skip_deps=true \ + skip_apps= \ + target_dir= \ + template= \ + template_dir= \ + tests=" if [[ ${cur} == --* ]] ; then COMPREPLY=( $(compgen -W "${lopts}" -- ${cur}) ) diff -Nru rebar-2.0.0/priv/shell-completion/fish/rebar.fish rebar-2.6.0/priv/shell-completion/fish/rebar.fish --- rebar-2.0.0/priv/shell-completion/fish/rebar.fish 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/shell-completion/fish/rebar.fish 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,189 @@ +## fish completions for rebar 2.5.0 + +function __fish_rebar_needs_command + set cmd (commandline -opc) + if [ (count $cmd) -eq 1 -a $cmd[1] = 'rebar' -o $cmd[1] = './rebar' ] + return 0 + end + return 1 +end + +function __fish_rebar_using_command + set cmd (commandline -opc) + if [ (count $cmd) -gt 1 ] + if [ $argv[1] = $cmd[2] ] + return 0 + end + end + return 1 +end + +## Rebar Command Output +## ➜ ~ rebar --version +## rebar 2.5.0 R16B02 20140716_213805 git 2.5.0-14-g1e7c742 +## ➜ ~ rebar --help +## Usage: rebar [-h] [-c] [-v ] [-q ] [-V] [-f] +## [-D ] [-j ] [-C ] [-p] [-k] +## [-r ] [var=value,...] +## +## -h, --help Show the program options +## -c, --commands Show available commands +## -v, --verbose Verbosity level (-v, -vv) +## -q, --quiet Quiet, only print error messages +## -V, --version Show version information +## -f, --force Force +## -D Define compiler macro +## -j, --jobs Number of concurrent workers a command may use. +## Default: 3 +## -C, --config Rebar config file to use +## -p, --profile Profile this run of rebar +## -k, --keep-going Keep running after a command fails +## -r, --recursive Apply commands to subdirs and dependencies +## var=value rebar global variables (e.g. force=1) +## command Command to run (e.g. compile) +## +## To see a list of built-in commands, execute rebar -c. +## +## Type 'rebar help ' for help on specific commands. + +# general options +complete -f -c rebar -n 'not __fish_rebar_needs_command' -l help -d 'Display the manual of a rebar command' + +## Flags +complete -c rebar -s h -l help -d "Show the program options" +complete -c rebar -s c -l commands -d "Show available commands" +complete -c rebar -s v -l verbose -d "Verbosity level (-v, -vv, -vvv, --verbose 3). Default: 0" +complete -c rebar -s vv -d "Verbosity level 2" +complete -c rebar -s vvv -d "Verbosity level 2" +complete -c rebar -s q -l quiet -d "Quiet, only print error messages" +complete -c rebar -s V -l version -d "Show version information" +complete -c rebar -s f -l force -d "Force" +complete -c rebar -s D -d "Define compiler macro" +complete -c rebar -s j -l jobs -d "Number of concurrent workers a command may use. Default: 3" +complete -c rebar -s C -l config -d "Rebar config file to use" +complete -c rebar -s p -l profile -d "Profile this run of rebar" +complete -c rebar -s k -l keep-going -d "Keep running after a command fails" +complete -c rebar -s r -l recursive -d "Apply commands to subdirs and dependencies" + + +## Not going to cover abbreviations, since this is for completions anyway :D +## rebar allows you to abbreviate the command to run: +## $ rebar co # same as rebar compile +## $ rebar eu # same as rebar eunit +## $ rebar g-d # same as rebar get-deps +## $ rebar x eu # same as rebar xref eunit +## $ rebar l-d # same as rebar list-deps +## $ rebar l-d l-t # same as rebar list-deps list-templates +## $ rebar list-d l-te # same as rebar list-deps list-templates +## +## Core command line options: +## apps=app1,app2 (specify apps to process) +## skip_apps=app1,app2 (specify apps to skip) + +## COMMANDS! +## ➜ ~ rebar -c +## clean Clean +complete -f -c rebar -n '__fish_rebar_needs_command' -a clean -d 'Clean' + +## compile Compile sources +complete -f -c rebar -n '__fish_rebar_needs_command' -a compile -d 'Compile sources' + +## escriptize Generate escript archive +complete -f -c rebar -n '__fish_rebar_needs_command' -a escriptize -d 'Generate escript archive' + +## create template= [var=foo,...] Create skel based on template and vars +complete -f -c rebar -n '__fish_rebar_needs_command' -a create -d 'Create skel based on template and vars' +complete -f -c rebar -n '__fish_rebar_using_command create' -a 'template=' -d 'Template name' + +## create-app [appid=myapp] Create simple app skel +complete -f -c rebar -n '__fish_rebar_needs_command' -a create-app -d 'Create simple app skel' +complete -f -c rebar -n '__fish_rebar_using_command create-app' -a 'appid=' -d 'Application name' + +## create-lib [libid=mylib] Create simple lib skel +complete -f -c rebar -n '__fish_rebar_needs_command' -a create-lib -d 'Create simple lib skel' +complete -f -c rebar -n '__fish_rebar_using_command create-lib' -a 'libid=' -d 'Library name' + +## create-node [nodeid=mynode] Create simple node skel +complete -f -c rebar -n '__fish_rebar_needs_command' -a create-node -d 'Create simple node skel' +complete -f -c rebar -n '__fish_rebar_using_command create-node' -a 'nodeid=' -d 'Node name' + +## list-templates List available templates +complete -f -c rebar -n '__fish_rebar_needs_command' -a list-templates -d 'List available templates' + +## doc Generate Erlang program documentation +complete -f -c rebar -n '__fish_rebar_needs_command' -a doc -d 'Generate Erlang program documentation' + +## prepare-deps Run 'rebar -r get-deps compile' +complete -f -c rebar -n '__fish_rebar_needs_command' -a prepare-deps -d 'Prepare Dependencies' + +## refresh-deps Run 'rebar -r update-deps compile' +complete -f -c rebar -n '__fish_rebar_needs_command' -a refresh-deps -d 'Refresh Dependencies' + +## check-deps Display to be fetched dependencies +complete -f -c rebar -n '__fish_rebar_needs_command' -a check-deps -d 'Display to be fetched dependencies' + +## get-deps Fetch dependencies +complete -f -c rebar -n '__fish_rebar_needs_command' -a get-deps -d 'Fetch dependencies' + +## update-deps Update fetched dependencies +complete -f -c rebar -n '__fish_rebar_needs_command' -a update-deps -d 'Update fetched dependencies' + +## delete-deps Delete fetched dependencies +complete -f -c rebar -n '__fish_rebar_needs_command' -a delete-deps -d 'Delete fetched dependencies' + +## list-deps List dependencies +complete -f -c rebar -n '__fish_rebar_needs_command' -a list-deps -d 'List Dependencies' + +## generate [dump_spec=0/1] Build release with reltool +complete -f -c rebar -n '__fish_rebar_needs_command' -a generate -d 'Build release with reltool' +complete -f -c rebar -n '__fish_rebar_using_command generate' -a 'dump_spec=0 dump_spec=1' + +## overlay Run reltool overlays only +complete -f -c rebar -n '__fish_rebar_needs_command' -a overlay -d 'Run reltool overlays only' + +## generate-upgrade previous_release=path Build an upgrade package +complete -f -c rebar -n '__fish_rebar_needs_command' -a generate-upgrade -d 'Build an upgrade package' +complete -f -c rebar -n '__fish_rebar_using_command generate-upgrade' -a 'previous_release=' + +## generate-appups previous_release=path Generate appup files +complete -f -c rebar -n '__fish_rebar_needs_command' -a generate-appups -d 'Generate appup files' +complete -f -c rebar -n '__fish_rebar_using_command generate-appups' -a 'previous_release=' + +## eunit [suite[s]=foo] Run EUnit tests in foo.erl and +## test/foo_tests.erl +## [suite[s]=foo] [test[s]=bar] Run specific EUnit tests [first test +## name starting with 'bar' in foo.erl +## and test/foo_tests.erl] +## [test[s]=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. +## [random_suite_order=true] Run tests in a random order, either +## [random_suite_order=Seed] with a random seed for the PRNG, or a +## specific one. +complete -f -c rebar -n '__fish_rebar_needs_command' -a eunit -d 'Run EUnit tests' +complete -f -c rebar -n '__fish_rebar_using_command eunit' -a 'suites=' +complete -f -c rebar -n '__fish_rebar_using_command eunit' -a 'tests=' +complete -f -c rebar -n '__fish_rebar_using_command eunit' -a 'random_suite_order=' + +## ct [suite[s]=] [case=] Run common_test suites +complete -f -c rebar -n '__fish_rebar_needs_command' -a ct -d 'Run common_test suites' +complete -f -c rebar -n '__fish_rebar_using_command ct' -a 'suites=' +complete -f -c rebar -n '__fish_rebar_using_command ct' -a 'case=' + +## qc Test QuickCheck properties +complete -f -c rebar -n '__fish_rebar_needs_command' -a qc -d 'Test QuickCheck properties' + +## xref Run cross reference analysis +complete -f -c rebar -n '__fish_rebar_needs_command' -a xref -d 'Run cross reference analysis' + +## shell Start a shell similar to +## 'erl -pa ebin -pa deps/*/ebin' +complete -f -c rebar -n '__fish_rebar_needs_command' -a shell -d 'Start a shell' + +## help Show the program options +complete -f -c rebar -n '__fish_rebar_needs_command' -a help -d 'Show the program options' + +## version Show version information +complete -f -c rebar -n '__fish_rebar_needs_command' -a version -d 'Show version information' diff -Nru rebar-2.0.0/priv/shell-completion/zsh/_rebar rebar-2.6.0/priv/shell-completion/zsh/_rebar --- rebar-2.0.0/priv/shell-completion/zsh/_rebar 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/shell-completion/zsh/_rebar 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,92 @@ +#compdef rebar + +local curcontext=$curcontext state ret=1 +typeset -ga _rebar_global_opts + +_rebar_global_opts=( + '(--help -h)'{--help,-h}'[Show the program options]' + '(--commands -c)'{--commands,-c}'[Show available commands]' + '(--version -V)'{--version,-V}'[Show version information]' + '(-vv -v)'--verbose'[Enforce verbosity level]' + '(-vv)-v[Slightly more verbose output]' + '(-v)-vv[More verbose output]' + '(-vv -v --verbose)'{--quiet,-q}'[Quiet, only print error messages]' + '(--force -f)'{--force,-f}'[Force]' + '-D+[Define compiler macro]' + '(--jobs -j)'{--jobs+,-j+}'[Number of concurrent workers a command may use. Default: 3]:workers:(1 2 3 4 5 6 7 8 9)' + '(--config -C)'{--config,-C}'[Rebar config file to use]:files:_files' + '(--profile -p)'{--profile,-p}'[Profile this run of rebar]' + '(--keep-going -k)'{--keep-going,-k}'[Keep running after a command fails]' + '(--recursive -r)'{--recursive,-r}'[Apply commands to subdirs and dependencies]' +) + +_rebar () { + _arguments -C $_rebar_global_opts \ + '*::command and variable:->cmd_and_var' \ + && return + + case $state in + cmd_and_var) + _values -S = 'variables' \ + 'dialyze[Analyze the code for discrepancies]' \ + 'build-plt[Build project-specific PLT]' \ + 'check-plt[Check the plt for consistency and rebuild it if it is not up-to-date]' \ + 'clean[Clean]' \ + 'compile[Compile sources]' \ + 'create[Create skel based on template and vars]' \ + 'create-app[Create simple app skel]' \ + 'create-lib[Create simple lib skel]' \ + 'create-node[Create simple node skel]' \ + 'list-template[List available templates]' \ + 'doc[Generate Erlang program documentation]' \ + 'check-deps[Display to be fetched dependencies]' \ + 'prepare-deps[Fetch and build dependencies]' \ + 'refresh-deps[Update and build dependencies]' \ + 'get-deps[Fetch dependencies]' \ + 'update-deps[Update fetched dependencies]' \ + 'delete-deps[Delete fetched dependencies]' \ + 'list-deps[List dependencies]' \ + 'generate[Build release with reltool]' \ + 'overlay[Run reltool overlays only]' \ + 'generate-appups[Generate appup files]' \ + 'generate-upgrade[Build an upgrade package]' \ + 'escriptize[Create stand-alone escript executable]' \ + 'eunit[Run eunit tests]' \ + 'ct[Run common_test suites]' \ + 'qc[Test QuickCheck properties]' \ + 'xref[Run cross reference analysis]' \ + 'help[Show the program options]' \ + 'version[Show version information]' \ + 'apps[Application names to process]:' \ + 'case[Common Test case]:' \ + 'dump_spec[Dump reltool spec]::flag:(1)' \ + 'jobs[Number of workers]::workers:(0 1 2 3 4 5 6 7 8 9)' \ + 'suites[Common Test suites]::suite name:_path_files -W "(src test)" -g "*.erl(:r)"' \ + 'verbose[Verbosity level]::verbosity level:(0 1 2 3)' \ + 'appid[Application id]:' \ + 'overlay_vars[Overlay variables file]:' \ + 'previous_release[Previous release path]:' \ + 'profiler[Select profiler]::flag:(fprof eflame)' \ + 'nodeid[Node id]:' \ + 'root_dir[Reltool config root directory]::directory:_files -/' \ + 'shell[Start a shell similar to erl -pa ebin -pa deps/*/ebin]' \ + 'skip_deps[Skip deps]::flag:(true false)' \ + 'skip_apps[Application names to not process]::flag:(true false)' \ + 'target_dir[Target directory]:' \ + 'template[Template name]:' \ + 'template_dir[Template directory]::directory:_files -/' \ + 'tests[Run eunit tests whose name starts with given string]:' \ + && ret=0 + ;; + esac +} + +_rebar + +# Local variables: +# mode: shell-script +# sh-basic-offset: 2 +# sh-indent-comment: t +# indent-tabs-mode: nil +# End: +# ex: sw=2 ts=2 et filetype=sh diff -Nru rebar-2.0.0/priv/templates/simpleapp_sup.erl rebar-2.6.0/priv/templates/simpleapp_sup.erl --- rebar-2.0.0/priv/templates/simpleapp_sup.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simpleapp_sup.erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,4 +1,3 @@ - -module({{appid}}_sup). -behaviour(supervisor). diff -Nru rebar-2.0.0/priv/templates/simpleevent.erl rebar-2.6.0/priv/templates/simpleevent.erl --- rebar-2.0.0/priv/templates/simpleevent.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/templates/simpleevent.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,60 @@ +-module({{eventid}}). +-behaviour(gen_event). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ + +-export([start_link/0, + add_handler/2]). + +%% ------------------------------------------------------------------ +%% gen_event Function Exports +%% ------------------------------------------------------------------ + +-export([init/1, + handle_event/2, + handle_call/2, + handle_info/2, + terminate/2, + code_change/3]). + +-record(state, {}). + +%% ------------------------------------------------------------------ +%% API Function Definitions +%% ------------------------------------------------------------------ + +start_link() -> + gen_event:start_link({local, ?MODULE}). + +add_handler(Handler, Args) -> + gen_event:add_handler(?MODULE, Handler, Args). + +%% ------------------------------------------------------------------ +%% gen_event Function Definitions +%% ------------------------------------------------------------------ + +init([]) -> + {ok, #state{}}. + +handle_event(_Event, State) -> + {ok, State}. + +handle_call(_Request, State) -> + Reply = ok, + {ok, Reply, State}. + +handle_info(_Info, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + diff -Nru rebar-2.0.0/priv/templates/simpleevent.template rebar-2.6.0/priv/templates/simpleevent.template --- rebar-2.0.0/priv/templates/simpleevent.template 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/templates/simpleevent.template 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,2 @@ +{variables, [{eventid, "myevent"}]}. +{template, "simpleevent.erl", "src/{{eventid}}.erl"}. diff -Nru rebar-2.0.0/priv/templates/simplelib.app.src rebar-2.6.0/priv/templates/simplelib.app.src --- rebar-2.0.0/priv/templates/simplelib.app.src 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplelib.app.src 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,14 @@ +{application, {{libid}}, + [ + {description, "An Erlang {{libid}} library"}, + {vsn, "1"}, + {modules, [ + {{libid}} + ]}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {env, []} + ]}. diff -Nru rebar-2.0.0/priv/templates/simplelib.erl rebar-2.6.0/priv/templates/simplelib.erl --- rebar-2.0.0/priv/templates/simplelib.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplelib.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,18 @@ +-module({{libid}}). + +%% {{libid}}: {{libid}} library's entry point. + +-export([my_func/0]). + + +%% API + +my_func() -> + ok(). + +%% Internals + +ok() -> + ok. + +%% End of Module. diff -Nru rebar-2.0.0/priv/templates/simplelib.template rebar-2.6.0/priv/templates/simplelib.template --- rebar-2.0.0/priv/templates/simplelib.template 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplelib.template 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,3 @@ +{variables, [{libid, "mylib"}]}. +{template, "simplelib.app.src", "src/{{libid}}.app.src"}. +{template, "simplelib.erl", "src/{{libid}}.erl"}. diff -Nru rebar-2.0.0/priv/templates/simplenode.erl.script rebar-2.6.0/priv/templates/simplenode.erl.script --- rebar-2.0.0/priv/templates/simplenode.erl.script 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.erl.script 2015-06-19 16:14:28.000000000 +0000 @@ -1,16 +1,26 @@ #!/bin/sh -## This script replaces the default "erl" in erts-VSN/bin. This is necessary -## as escript depends on erl and in turn, erl depends on having access to a -## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect -## of running escript -- the embedded node bypasses erl and uses erlexec directly -## (as it should). +# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. +if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then + POSIX_SHELL="true" + export POSIX_SHELL + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh as well +unset POSIX_SHELL + +## This script replaces the default "erl" in erts-VSN/bin. This is +## necessary as escript depends on erl and in turn, erl depends on +## having access to a bootscript (start.boot). Note that this script +## is ONLY invoked as a side-effect of running escript -- the embedded +## node bypasses erl and uses erlexec directly (as it should). ## -## Note that this script makes the assumption that there is a start_clean.boot -## file available in $ROOTDIR/release/VSN. +## Note that this script makes the assumption that there is a +## start_clean.boot file available in $ROOTDIR/release/VSN. # Determine the abspath of where this script is executing from. -ERTS_BIN_DIR=$(cd ${0%/*} && pwd) +ERTS_BIN_DIR=$(cd ${0%/*} && pwd -P) # Now determine the root directory -- this script runs from erts-VSN/bin, # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR diff -Nru rebar-2.0.0/priv/templates/simplenode.install_upgrade.escript rebar-2.6.0/priv/templates/simplenode.install_upgrade.escript --- rebar-2.0.0/priv/templates/simplenode.install_upgrade.escript 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.install_upgrade.escript 2015-06-19 16:14:28.000000000 +0000 @@ -2,43 +2,54 @@ %%! -noshell -noinput %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et +%% This file is left for backward-compatibility. +%% You, probably, shouldn't include it to new projects. --define(TIMEOUT, 60000). --define(INFO(Fmt,Args), io:format(Fmt,Args)). main([NodeName, Cookie, ReleasePackage]) -> - TargetNode = start_distribution(NodeName, Cookie), - {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, - [ReleasePackage], ?TIMEOUT), - ?INFO("Unpacked Release ~p~n", [Vsn]), - {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, - check_install_release, [Vsn], ?TIMEOUT), - {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, - install_release, [Vsn], ?TIMEOUT), - ?INFO("Installed Release ~p~n", [Vsn]), - ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), - ?INFO("Made Release ~p Permanent~n", [Vsn]); + io:format("WARNING: 'install_upgrade.escript' is deprecated! " + "Use 'nodetool upgrade' instead.~n"), + NodeRoot = filename:dirname(filename:dirname(escript:script_name())), + NodeTool = which_nodetool(NodeRoot), + process_flag(trap_exit, true), + Port = erlang:open_port( + {spawn_executable, NodeTool}, + [{args, ["-sname", NodeName, + "-setcookie", Cookie, + "upgrade", ReleasePackage]}, + binary, exit_status, use_stdio, stderr_to_stdout, hide]), + port_loop(Port); main(_) -> - init:stop(1). + halt(1). -start_distribution(NodeName, Cookie) -> - MyNode = make_script_node(NodeName), - {ok, _Pid} = net_kernel:start([MyNode, shortnames]), - erlang:set_cookie(node(), list_to_atom(Cookie)), - TargetNode = make_target_node(NodeName), - case {net_kernel:hidden_connect_node(TargetNode), - net_adm:ping(TargetNode)} of - {true, pong} -> - ok; - {_, pang} -> - io:format("Node ~p not responding to pings.\n", [TargetNode]), - init:stop(1) - end, - TargetNode. -make_target_node(Node) -> - [_, Host] = string:tokens(atom_to_list(node()), "@"), - list_to_atom(lists:concat([Node, "@", Host])). +which_nodetool(NodeRoot) -> + %% ${RELEASE_ROOT}/ + %% bin/install_upgrade.escript + %% bin/nodetool ? + %% erts-/bin/nodetool ? + %% releases//nodetool ? + %% releases/start_erl.data + {ok, Content} = file:read_file(filename:join([NodeRoot, "releases", "start_erl.data"])), + [ErtsVsn, AppVsn] = binary:split(Content, <<" ">>), + Probes = [ + filename:join([NodeRoot, "bin", "nodetool"]), + filename:join([NodeRoot, <<"erts-", ErtsVsn/binary>>, "bin", "nodetool"]), + filename:join([NodeRoot, "releases", AppVsn, "bin", "nodetool"]) + ], + case lists:dropwhile(fun(Path) -> not filelib:is_regular(Path) end, Probes) of + [] -> + io:format("ERROR: can't find 'nodetool' in ~p.~n", [Probes]), + halt(2); + [Path | _] -> + Path + end. -make_script_node(Node) -> - list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])). +port_loop(Port) -> + receive + {Port, {data, Data}} -> + io:put_chars(Data), + port_loop(Port); + {Port, {exit_status, Status}} -> + halt(Status) + end. diff -Nru rebar-2.0.0/priv/templates/simplenode.nodetool rebar-2.6.0/priv/templates/simplenode.nodetool --- rebar-2.0.0/priv/templates/simplenode.nodetool 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.nodetool 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,4 @@ +#!/usr/bin/env escript %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et %% ------------------------------------------------------------------- @@ -5,25 +6,51 @@ %% nodetool: Helper Script for interacting with live nodes %% %% ------------------------------------------------------------------- - main(Args) -> ok = start_epmd(), %% Extract the args {RestArgs, TargetNode} = process_args(Args, [], undefined), + %% any commands that don't need a running node + case RestArgs of + ["chkconfig", File] -> + case file:consult(File) of + {ok, _} -> + io:format("ok\n"), + halt(0); + {error, {Line, Mod, Term}} -> + io:format(standard_error, ["Error on line ", + file:format_error({Line, Mod, Term}), "\n"], []), + halt(1); + {error, R} -> + io:format(standard_error, ["Error reading config file: ", + file:format_error(R), "\n"], []), + halt(1) + end; + _ -> + ok + end, + %% See if the node is currently running -- if it's not, we'll bail - case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + case {net_kernel:hidden_connect_node(TargetNode), + net_adm:ping(TargetNode)} of {true, pong} -> ok; + {false,pong} -> + io:format("Failed to connect to node ~p .\n", [TargetNode]), + halt(1); {_, pang} -> io:format("Node ~p not responding to pings.\n", [TargetNode]), halt(1) end, case RestArgs of + ["getpid"] -> + io:format("~p\n", + [list_to_integer(rpc:call(TargetNode, os, getpid, []))]); ["ping"] -> - %% If we got this far, the node already responsed to a ping, so just dump - %% a "pong" + %% If we got this far, the node already responsed to a + %% ping, so just dump a "pong" io:format("pong\n"); ["stop"] -> io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); @@ -32,7 +59,9 @@ ["reboot"] -> io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); ["rpc", Module, Function | RpcArgs] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), [RpcArgs], 60000) of ok -> ok; @@ -42,8 +71,23 @@ _ -> halt(1) end; + ["rpc_infinity", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), + [RpcArgs], infinity) of + ok -> + ok; + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; ["rpcterms", Module, Function, ArgsAsString] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), consult(ArgsAsString), 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), @@ -51,9 +95,41 @@ Other -> io:format("~p\n", [Other]) end; + ["eval", Str0] -> + Str = string:strip(Str0, right, $.) ++ ".", + Bindings = erl_eval:new_bindings(), + case rpc:call(TargetNode, + erl_eval, + exprs, + [parse(Str), Bindings], + 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + {value, Value, _Bindings} -> + io:format("~p\n", [Value]) + end; + ["upgrade", ReleasePackage] -> + %% TODO: This script currently does NOT support slim releases. + %% Necessary steps to upgrade a slim release are as follows: + %% 1. unpack relup archive manually + %% 2. copy releases directory and necessary libraries + %% 3. using release_hander:set_unpacked/2 . + %% For more details, see https://github.com/rebar/rebar/pull/52 + %% and https://github.com/rebar/rebar/issues/202 + {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], 60000), + io:format("Unpacked Release ~p\n", [Vsn]), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + check_install_release, [Vsn], 60000), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + install_release, [Vsn], 60000), + io:format("Installed Release ~p\n", [Vsn]), + ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], 60000), + io:format("Made Release ~p Permanent\n", [Vsn]); Other -> io:format("Other: ~p\n", [Other]), - io:format("Usage: nodetool {ping|stop|restart|reboot}\n") + io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval|upgrade}\n") end, net_kernel:stop(). @@ -136,3 +212,8 @@ {more, Cont1} -> consult(Cont1, eof, Acc) end. + +parse(Str) -> + {ok, Tokens, _} = erl_scan:string(Str), + {ok, Exprs} = erl_parse:parse_exprs(Tokens), + Exprs. diff -Nru rebar-2.0.0/priv/templates/simplenode.reltool.config rebar-2.6.0/priv/templates/simplenode.reltool.config --- rebar-2.0.0/priv/templates/simplenode.reltool.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.reltool.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,5 @@ +%% -*- mode: erlang -*- +%% ex: ft=erlang {sys, [ {lib_dirs, []}, {erts, [{mod_cond, derived}, {app_file, strip}]}, @@ -16,15 +18,13 @@ ]}, {boot_rel, "{{nodeid}}"}, {profile, embedded}, - {incl_cond, exclude}, + {incl_cond, derived}, {excl_archive_filters, [".*"]}, %% Do not archive built libs - {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)", + {excl_sys_filters, ["^bin/(?!start_clean.boot)", + "^erts.*/bin/(dialyzer|typer)", "^erts.*/(doc|info|include|lib|man|src)"]}, {excl_app_filters, ["\.gitignore"]}, - {app, sasl, [{incl_cond, include}]}, - {app, stdlib, [{incl_cond, include}]}, - {app, kernel, [{incl_cond, include}]}, - {app, {{nodeid}}, [{incl_cond, include}]} + {app, {{nodeid}}, [{mod_cond, app}, {incl_cond, include}]} ]}. {target_dir, "{{nodeid}}"}. @@ -32,10 +32,13 @@ {overlay, [ {mkdir, "log/sasl"}, {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"}, - {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"}, + {copy, "files/nodetool", "releases/\{\{rel_vsn\}\}/nodetool"}, + {copy, "{{nodeid}}/bin/start_clean.boot", + "\{\{erts_vsn\}\}/bin/start_clean.boot"}, {copy, "files/{{nodeid}}", "bin/{{nodeid}}"}, {copy, "files/{{nodeid}}.cmd", "bin/{{nodeid}}.cmd"}, {copy, "files/start_erl.cmd", "bin/start_erl.cmd"}, + %% Following line may be safely removed in new projects {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"}, {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"}, {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"} diff -Nru rebar-2.0.0/priv/templates/simplenode.runner rebar-2.6.0/priv/templates/simplenode.runner --- rebar-2.0.0/priv/templates/simplenode.runner 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.runner 2015-06-19 16:14:28.000000000 +0000 @@ -2,25 +2,42 @@ # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et -RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) +# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. +if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then + POSIX_SHELL="true" + export POSIX_SHELL + # To support 'whoami' add /usr/ucb to path + PATH=/usr/ucb:$PATH + export PATH + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh +unset POSIX_SHELL + +RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd -P) +RUNNER_SCRIPT=${0##*/} + +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= +WHOAMI=$(whoami) # Make sure this script is running as the appropriate user -if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then - exec sudo -u $RUNNER_USER -i $0 $@ +if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then + type sudo > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 + exit 1 + fi + echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 + exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ 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,22 +46,37 @@ 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 -NAME_ARG=`egrep '^-s?name' $VMARGS_PATH` +NAME_ARG=`egrep '^\-s?name' $VMARGS_PATH` if [ -z "$NAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 @@ -54,67 +86,168 @@ REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` -# Note the `date +%s`, used to allow multiple remsh to the same node transparently -REMSH_NAME_ARG="$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`" -REMSH_REMSH_ARG="-remsh $REMSH_NAME" +# Test if REMSH_NAME contains a @ and set REMSH_HOSTNAME_PART +# and REMSH_NAME_PART according REMSH_TYPE +MAYBE_FQDN_HOSTNAME=`hostname` +HOSTNAME=`echo $MAYBE_FQDN_HOSTNAME | awk -F. '{print $1}'` + +REMSH_HOSTNAME_PART="$MAYBE_FQDN_HOSTNAME" +case "$REMSH_NAME" in + *@*) + REMSH_HOSTNAME_PART=`echo $REMSH_NAME | awk -F@ '{print $2}'` + REMSH_NAME_PART=`echo $REMSH_NAME | awk -F@ '{print $1}'` + ;; + *) + REMSH_NAME_PART="$REMSH_NAME" + if [ "$REMSH_TYPE" = "-sname" ]; then + REMSH_HOSTNAME_PART="$HOSTNAME" + else + # -name type, check if `hostname` is fqdn + if [ "$MAYBE_FQDN_HOSTNAME" = "$HOSTNAME" ]; then + echo "Hostname must be a fqdn domain name when node is configured with long names" + echo "and the full node name isn't configured in vm.args" + exit 1 + fi + fi + ;; +esac + +# Note the `date +%s`, used to allow multiple remsh to the same node +# transparently +REMSH_NAME_ARG="$REMSH_TYPE remsh`date +%s`@$REMSH_HOSTNAME_PART" +REMSH_REMSH_ARG="-remsh $REMSH_NAME_PART@$REMSH_HOSTNAME_PART" # Extract the target cookie -COOKIE_ARG=`grep '^-setcookie' $VMARGS_PATH` +COOKIE_ARG=`grep '^\-setcookie' $VMARGS_PATH` if [ -z "$COOKIE_ARG" ]; then echo "vm.args needs to have a -setcookie parameter." exit 1 fi -# Add ERTS bin dir to our path -ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin +# Make sure CWD is set to the right dir +cd $USE_DIR + +# Make sure log directory exists +mkdir -p $USE_DIR/log + +RUNNER_SCRIPT_DATA= +if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/runner_script.data" ]; then + RUNNER_SCRIPT_DATA=`cat $RUNNER_BASE_DIR/releases/$APP_VSN/runner_script.data` +fi -# Setup command to control the node -NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" +if [ -z "$RUNNER_SCRIPT_DATA" ]; then + ROOTDIR=$RUNNER_BASE_DIR + ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/nodetool" ]; then + NODETOOL="$ERTS_PATH/escript $RUNNER_BASE_DIR/releases/$APP_VSN/nodetool $NAME_ARG $COOKIE_ARG" + else + NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" + fi + SLIM_ARGS= +elif [ "$RUNNER_SCRIPT_DATA" = "slim" ]; then + # Setup system paths + SYSTEM_ERL_PATH=`which erl` + if [ ! -x "$SYSTEM_ERL_PATH" ]; then + echo "Failed to find erl. Is Erlang/OTP available in PATH?" + exit 1 + fi + SYSTEM_HOME_BIN=${SYSTEM_ERL_PATH%/*} + ROOTDIR=$SYSTEM_HOME_BIN/../lib/erlang + ERTS_PATH=$ROOTDIR/erts-$ERTS_VSN/bin + unset SYSTEM_ERL_PATH + unset SYSTEM_HOME_BIN + + LOCAL_ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + NODETOOL="$ERTS_PATH/escript $RUNNER_BASE_DIR/releases/$APP_VSN/nodetool $NAME_ARG $COOKIE_ARG" + unset LOCAL_ERL_PATH + + # Setup additional arguments for slim release + SLIM_ARGS="-boot_var RELTOOL_EXT_LIB $RUNNER_BASE_DIR/lib -sasl releases_dir \"$RUNNER_BASE_DIR/releases\"" +else + echo "Unknown runner_script.data" + exit 1 +fi # Setup remote shell command to control node -REMSH="$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG" +REMSH="$ERTS_PATH/erl -hidden $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG" + +# Common functions + +# Ping node without allowing nodetool to take stdin +ping_node() { + $NODETOOL ping < /dev/null +} + +# Set the PID global variable, return 1 on error +get_pid() { + PID=`$NODETOOL getpid < /dev/null` + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + return 1 + fi + + # don't allow empty or init pid's + if [ -z $PID ] || [ "$PID" -le 1 ]; then + return 1 + fi + + return 0 +} # 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` + RES=`ping_node` 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) # Wait for the node to completely stop... case `uname -s` in - Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) - # PID COMMAND - PID=`ps ax -o pid= -o command=|\ - grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` - ;; - SunOS) - # PID COMMAND - PID=`ps -ef -o pid= -o args=|\ - grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` - ;; - CYGWIN*) - # UID PID PPID TTY STIME COMMAND - PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` - ;; + Darwin) + # Make sure we explicitly set this because iTerm.app doesn't for + # some reason. + COMMAND_MODE=unix2003 esac + + # Get the PID from nodetool + get_pid + GPR=$? + if [ "$GPR" -ne 0 ] || [ -z $PID ]; then + exit $GPR + fi + + # Tell nodetool to initiate a stop $NODETOOL stop ES=$? if [ "$ES" -ne 0 ]; then exit $ES fi - while `kill -0 $PID 2>/dev/null`; + + # Wait for the node to completely stop... + while `kill -s 0 $PID 2>/dev/null` do sleep 1 done @@ -140,7 +273,7 @@ ping) ## See if the VM is alive - $NODETOOL ping + ping_node ES=$? if [ "$ES" -ne 0 ]; then exit $ES @@ -148,8 +281,8 @@ ;; attach) - # Make sure a node IS running - RES=`$NODETOOL ping` + # Make sure a node is running + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" @@ -160,9 +293,26 @@ exec $ERTS_PATH/to_erl $PIPE_DIR ;; - remote_console) + eval) # Make sure a node IS running - RES=`$NODETOOL ping` + ping_node > /dev/null 2>&1 + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + shift + $NODETOOL eval "$1" + ES_EVAL=$? + if [ "$ES_EVAL" -ne 0 ]; then + exit $ES_EVAL + fi + ;; + + remote_console) + # Make sure a node is running + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" @@ -182,32 +332,34 @@ fi # Make sure a node IS running - RES=`$NODETOOL ping` + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" exit $ES fi - node_name=`echo $NAME_ARG | awk '{print $2}'` - erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` - - $ERTS_PATH/escript $RUNNER_BASE_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 + $NODETOOL upgrade $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 BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin EMU=beam PROGNAME=`echo $0 | sed 's/.*\\///'` - CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH" + CMD="$BINDIR/erlexec $SLIM_ARGS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH" export EMU export ROOTDIR export BINDIR @@ -232,11 +384,10 @@ FOREGROUNDOPTIONS="-noinput +Bd" # Setup beam-required vars - ROOTDIR=$RUNNER_BASE_DIR BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin EMU=beam PROGNAME=`echo $0 | sed 's/.*\///'` - CMD="$BINDIR/erlexec $FOREGROUNDOPTIONS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH" + CMD="$BINDIR/erlexec $SLIM_ARGS $FOREGROUNDOPTIONS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH" export EMU export ROOTDIR export BINDIR @@ -249,8 +400,17 @@ # Start the VM exec $CMD -- ${1+"$@"} ;; + getpid) + # Get the PID from nodetool + get_pid + ES=$? + if [ "$ES" -ne 0 ] || [ -z $PID ]; then + exit $ES + fi + echo $PID + ;; *) - echo "Usage: $SCRIPT {start|foreground|stop|restart|reboot|ping|console|console_clean|attach|remote_console|upgrade}" + echo "Usage: $SCRIPT {start|start_boot |foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot |attach|eval|remote_console|upgrade}" exit 1 ;; esac diff -Nru rebar-2.0.0/priv/templates/simplenode.windows.runner.cmd rebar-2.6.0/priv/templates/simplenode.windows.runner.cmd --- rebar-2.0.0/priv/templates/simplenode.windows.runner.cmd 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.windows.runner.cmd 2015-06-19 16:14:28.000000000 +0000 @@ -6,22 +6,35 @@ @rem which is assumed to be the node root. @for /F "delims=" %%I in ("%~dp0..") do @set node_root=%%~fI +@rem CWD to the node root directory +@cd %node_root% + @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" +@set nodetool="%erts_bin%\nodetool" + @if "%1"=="usage" @goto usage @if "%1"=="install" @goto install @if "%1"=="uninstall" @goto uninstall @@ -29,44 +42,53 @@ @if "%1"=="stop" @goto stop @if "%1"=="restart" @call :stop && @goto start @if "%1"=="console" @goto console +@if "%1"=="ping" @goto ping @if "%1"=="query" @goto query @if "%1"=="attach" @goto attach @if "%1"=="upgrade" @goto upgrade @echo Unknown command: "%1" :usage -@echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|query^|attach^|upgrade] +@echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|ping^|query^|attach^|upgrade] @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 + +:ping +@%escript% %nodetool% ping -sname "%node_name%" -setcookie "%erlang_cookie%" +@exit %ERRORLEVEL% @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 +98,7 @@ @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% %nodetool% -sname "%node_name%" -setcookie "%erlang_cookie%" upgrade %2 @goto :EOF :set_trim diff -Nru rebar-2.0.0/priv/templates/simplenode.windows.start_erl.cmd rebar-2.6.0/priv/templates/simplenode.windows.start_erl.cmd --- rebar-2.0.0/priv/templates/simplenode.windows.start_erl.cmd 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/priv/templates/simplenode.windows.start_erl.cmd 2015-06-19 16:14:28.000000000 +0000 @@ -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 -Nru rebar-2.0.0/README.md rebar-2.6.0/README.md --- rebar-2.0.0/README.md 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/README.md 2015-06-19 16:14:28.000000000 +0000 @@ -1,27 +1,28 @@ rebar ===== -rebar is an Erlang build tool that makes it easy to compile and -test Erlang applications, port drivers and releases. +rebar is an Erlang build tool that makes it easy to compile and test Erlang +applications, port drivers and releases. -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 -configuration work. rebar also provides dependency management, enabling -application writers to easily re-use common libraries from a variety of +[![Build Status](https://secure.travis-ci.org/rebar/rebar.png?branch=master)](http://travis-ci.org/rebar/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 +configuration work. rebar also provides dependency management, enabling +application writers to easily re-use common libraries from a variety of locations (git, hg, etc). Building -------- -Information on building and installing [Erlang/OTP](http://www.erlang.org) -can be found [here](https://github.com/erlang/otp/wiki/Installation) -([more info](https://github.com/erlang/otp/blob/master/INSTALL.md)). +Information on building and installing [Erlang/OTP](http://www.erlang.org) can +be found [here](https://github.com/erlang/otp/wiki/Installation) ([more +info](https://github.com/erlang/otp/blob/master/HOWTO/INSTALL.md)). ### Dependencies -To build rebar you will need a working installation of Erlang R13B03 (or -later). +To build rebar you will need a working installation of Erlang R13B03 (or later). Should you want to clone the rebar repository, you will also require git. @@ -29,12 +30,12 @@ You can download a pre-built binary version of rebar from: -https://github.com/basho/rebar/wiki/rebar +https://github.com/rebar/rebar/wiki/rebar #### Building rebar ```sh -$ git clone git://github.com/basho/rebar.git +$ git clone git://github.com/rebar/rebar.git $ cd rebar $ ./bootstrap Recompile: src/getopt @@ -50,98 +51,29 @@ Contributing to rebar ===================== -Pull requests and branching ---------------------------- - -Use one topic branch per pull request. - -Do not commit to master in your fork. - -Provide a clean branch without any merge commits from upstream. - -Usually you should squash any intermediate commits into the original single commit. - -Code style ----------- - -Do not introduce trailing whitespace. - -Do not mix spaces and tabs. +Please refer to [CONTRIBUTING](CONTRIBUTING.md). -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. - -Writing Commit Messages +Community and Resources ----------------------- -Structure your commit message like this: - -
-One line summary (less than 50 characters)
-
-Longer description (wrap at 72 characters)
-
- -### Summary - -* Less than 50 characters -* What was changed -* Imperative present tense (fix, add, change) - * `Fix bug 123` - * `Add 'foobar' command` - * `Change default timeout to 123` -* No period - -### Description - -* Wrap at 72 characters -* Why, explain intention and implementation approach -* Present tense - -### Atomicity - -* Break up logical changes -* Make whitespace changes separately - -Dialyzer and Tidier -------------------- - -Before you submit a patch 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: - -```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). - -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. +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/rebar/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/rebar/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/rebar/rebar/wiki) +- [issues](https://github.com/rebar/rebar/issues) diff -Nru rebar-2.0.0/rebar.config rebar-2.6.0/rebar.config --- rebar-2.0.0/rebar.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/rebar.config 2015-06-19 16:14:28.000000000 +0000 @@ -1,15 +1,44 @@ %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et -{app_bin, ["priv/rebar"]}. -{erl_opts, [warnings_as_errors]}. +%% 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/*", "."}]}. + +%% Types dict:dict() and digraph:digraph() have been introduced in Erlang 17. +%% At the same time, their counterparts dict() and digraph() are to be +%% deprecated in Erlang 18. namespaced_types option is used to select proper +%% type name depending on the OTP version used. +{erl_opts, + [ + {platform_define, "^[0-9]+", namespaced_types} + ]}. + {xref_checks, []}. {xref_queries, [{"(XC - UC) || (XU - X - B - (\"escript\":\"foldl\"/\"3\") + - (\"eunit_test\":\"function_wrapper\"/\"2\") + - (\"eflame\":\"apply\"/\"5\") - (\"abnfc\":\"file\"/\"2\") - (\"erlydtl\":\"compile\"/\"3\") - (\"lfe_comp\":\"file\"/\"2\") - (\"neotoma\":\"file\"/\"2\") - - (\"protobuffs_compile\":\"scan_file\"/\"1\"))", + - (\"protobuffs_compile\":\"scan_file\"/\"2\") + - (\"gpb_compile\":\"file\"/\"2\") + - (\"gpb_compile\":\"format_error\"/\"1\") + - (\"diameter_codegen\":\"from_dict\"/\"4\") + - (\"diameter_dict_util\":\"format_error\"/\"1\") + - (\"diameter_dict_util\":\"parse\"/\"2\"))", []}]}. + +{dialyzer, + [ + {plt_extra_apps, [diameter]}, + {warnings, + [ + unmatched_returns, + error_handling, + race_conditions + ]} + ]}. diff -Nru rebar-2.0.0/rebar.config.sample rebar-2.6.0/rebar.config.sample --- rebar-2.0.0/rebar.config.sample 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/rebar.config.sample 2015-06-19 16:14:28.000000000 +0000 @@ -5,6 +5,14 @@ %% == Core == +%% Extend list of always recursive commands +{recursive_cmds, []}. + +%% Check required ERTS or OTP release version +{require_erts_vsn, ".*"}. +{require_otp_vsn, ".*"}. +{require_min_otp_vsn, ".*"}. + %% Additional library directories to add to the code path {lib_dirs, []}. @@ -13,10 +21,12 @@ %% Erlang files to compile before the rest. Rebar automatically compiles %% parse_transforms and custom behaviours before anything other than the files %% in this list. -{erl_first_files, ["mymib1", "mymib2"]}. +{erl_first_files, ["src/mymib1.erl", "src/mymib2.erl"]}. %% Erlang compiler options -{erl_opts, [no_debug_info, {i, "myinclude"}, {src_dirs, ["src1", "src2"]}, +{erl_opts, [no_debug_info, + {i, "myinclude"}, + {src_dirs, ["src", "src2", "src3"]}, {platform_define, "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'}, {platform_define, "(linux|freebsd)", 'BACKLOG', 128}, @@ -28,6 +38,18 @@ %% SNMP mibs to compile first? {mib_first_files, []}. +%% leex options +{xrl_opts, []}. + +%% leex files to compile first +{xrl_first_files, []}. + +%% yecc options +{yrl_opts, []}. + +%% yecc files to compile first +{yrl_first_files, []}. + %% == EDoc == %% EDoc options @@ -46,7 +68,14 @@ %% architecture as a filter. {port_specs, [{"priv/so_name.so", ["c_src/*.c"]}, {"linux", "priv/hello_linux", ["c_src/hello_linux.c"]}, - {"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}}. + {"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}]}. + +%% == escriptize == +{escript_name, "application"}. +{escript_incl_apps, []}. +{escript_shebang, "#!/usr/bin/env escript\n"}. +{escript_comment, "%%\n"}. +{escript_emu_args, "%%! -pa application/application/ebin\n"}. %% == LFE Compiler == @@ -60,46 +89,83 @@ %% Options for the ErlyDTL compiler {erlydtl_opts, []}. +%% == Proto compiler == +{proto_opts, [ + {compiler, protobuffs}, + {src_dirs, ["src"]} + ]}. +%% Available compilers for protocol buffer files (*.proto): +%% protobuffs (default) +%% gpb +%% Optional src_dirs which is a list of directories where +%% to look for .proto files, default is src + +%% Options for the gpb protocol buffer compiler, +%% if selected by the proto_compiler option +{gpb_opts, []}. + +%% == Diameter compiler == + +%% Diameter files to compile before the rest +{dia_first_files, []}. + +%% Options for the diameter compiler +{dia_opts, []}. + %% == EUnit == %% 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 {clean_files, ["file", "file2"]}. -%% == Reltool == - -%% Target directory for the release -{target, "target"}. - %% == OTP Applications == -%% Binaries to link into the erlang path? -{app_bin, []}. - %% Enable validation of the OTP app module list. Default is 'true' {validate_app_modules, true}. @@ -111,11 +177,42 @@ %% 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. -{deps, [application_name, - {application_name, "1.0.*"}, - {application_name, "1.0.*", - {git, "git://github.com/basho/rebar.git", {branch, "master"}}}]}. +%% type, location and revision). +%% Rebar currently supports git, hg, bzr, svn, rsync, fossil, and p4. +{deps, [app_name, + {rebar, "1.0.*"}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git"}}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git", "Rev"}}, + {rebar, "1.0.*", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, + {rebar, "1.0.0", + {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, + %% 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. + {rebar, "", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, + [raw]}, + {app_name, ".*", {hg, "https://www.example.org/url"}}, + {app_name, ".*", {rsync, "Url"}}, + {app_name, ".*", {svn, "https://www.example.org/url"}}, + {app_name, ".*", {svn, "svn://svn.example.org/url"}}, + {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, + {app_name, ".*", {fossil, "https://www.example.org/url"}}, + {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, + {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]}. %% == Subdirectories == @@ -151,14 +248,34 @@ {xref_warnings, false}. +%% optional extra paths to include in xref:set_library_path/2. +%% specified relative location of rebar.config. +%% e.g. {xref_extra_paths,["../gtknode/src"]} +{xref_extra_paths,[]}. + %% xref checks to run -{xref_checks, [exports_not_used, undefined_function_calls]}. +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions]}. %% Optional custom xref queries (xref manual has details) specified as %% {xref_queries, [{query_string(), expected_query_result()},...]} -%% The following for example removes all references to ejabberd:*_msg/4 +%% The following for example removes all references to mod:*foo/4 %% functions from undefined external function calls as those are in a %% generated module {xref_queries, [{"(XC - UC) || (XU - X - B" - " - (\"ejabberd_logger\":\".*_msg\"/\"4\"))",[]}]}. + " - (\"mod\":\".*foo\"/\"4\"))",[]}]}. + +%% == Dialyzer == + +{dialyzer, + [ + %% Store PLT locally inside the project in .rebar (Default) + {plt_location, local}, + %% Store PLT in custom directory + {plt_location, "custom_dir"}, + %% Extra apps to include in the PLT + {plt_extra_apps, [app1, app2]}, + {warnings, [unmatched_returns, error_handling]} + ]}. diff -Nru rebar-2.0.0/rebar.config.script rebar-2.6.0/rebar.config.script --- rebar-2.0.0/rebar.config.script 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/rebar.config.script 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,17 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et + +ExtraDeps = [{retest, ".*", {git, "git://github.com/dizzyd/retest.git"}}], + +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 -Nru rebar-2.0.0/RELEASE-NOTES.md rebar-2.6.0/RELEASE-NOTES.md --- rebar-2.0.0/RELEASE-NOTES.md 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/RELEASE-NOTES.md 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,186 @@ +# 2.6.0 + +* rebar/203: [Pluggable proto compilers gpb](https://github.com/rebar/rebar/pull/203) +* rebar/273: [Use target_dir as source of new version in generate-appups](https://github.com/rebar/rebar/pull/273) +* rebar/293: [Check C source dependencies in needs_compile](https://github.com/rebar/rebar/pull/293) +* rebar/305: [Fix compiler invocation on multiarch Linux](https://github.com/rebar/rebar/pull/305) +* rebar/322: [Treat vsn mismatch as warning if -k/--keep-going](https://github.com/rebar/rebar/pull/322) +* rebar/336: [Add details on Dialyzer with "make check"](https://github.com/rebar/rebar/pull/336) +* rebar/337: [Implement eflame -p/--profile support](https://github.com/rebar/rebar/pull/337) +* rebar/338: [Processing .app.src.script expects a single value to be returned.](https://github.com/rebar/rebar/pull/338) +* rebar/344: [Manually clean up paths.](https://github.com/rebar/rebar/pull/344) +* rebar/351: [fish shell completions for rebar](https://github.com/rebar/rebar/pull/351) +* rebar/352: [Add typer target (rebase of #309)](https://github.com/rebar/rebar/pull/352) +* rebar/354: [compiler respects 'keep_going' flag](https://github.com/rebar/rebar/pull/354) +* rebar/355: [Fix 'make build_plt'](https://github.com/rebar/rebar/pull/355) +* rebar/356: [Fix minor typo in CONTRIBUTING.md](https://github.com/rebar/rebar/pull/356) +* rebar/360: [Minor follow-up fixes for #293](https://github.com/rebar/rebar/pull/360) +* rebar/368: [Escape more characters in path (fix #367)](https://github.com/rebar/rebar/pull/368) +* rebar/371: [Fix cover print truncation when coverage is 100%](https://github.com/rebar/rebar/pull/371) +* rebar/372: [Implement eval command via nodetool](https://github.com/rebar/rebar/pull/372) +* rebar/376: [Remove check adding ebin to path for edoc target](https://github.com/rebar/rebar/pull/376) +* rebar/378: [deps: fix delete-deps if deps_dir ends with dot](https://github.com/rebar/rebar/pull/378) +* rebar/382: [Adapt dialyzer_reference to ba466e2d changes](https://github.com/rebar/rebar/pull/382) +* rebar/385: [Fix Dialyzer warning introduced in 0caf047f](https://github.com/rebar/rebar/pull/385) +* rebar/386: [Recompile .proto files with gpb also with prefix/suffix](https://github.com/rebar/rebar/pull/386) +* rebar/386: [Recompile .proto files with gpb also with prefix/suffix](https://github.com/rebar/rebar/pull/386) +* rebar/386: [Recompile .proto files with gpb also with prefix/suffix](https://github.com/rebar/rebar/pull/386) +* rebar/399: [Increase the timeout for the inttest/proto_gpb](https://github.com/rebar/rebar/pull/399) +* rebar/400: [add Emacs/vi header to non-dummy test modules](https://github.com/rebar/rebar/pull/400) +* rebar/403: [Update reference to installation notes](https://github.com/rebar/rebar/pull/403) +* rebar/404: [Consistently format export attributes](https://github.com/rebar/rebar/pull/404) +* rebar/405: [Generate cachegrind file if erlgrind is available](https://github.com/rebar/rebar/pull/405) +* rebar/406: [Fix deps path check in rebar_ct:collect_glob/3](https://github.com/rebar/rebar/pull/406) +* rebar/408: [Fix/remove duplicate console message](https://github.com/rebar/rebar/pull/408) +* rebar/411: [Fix app.config argument passing when using ct_run (take 2)](https://github.com/rebar/rebar/pull/411) +* rebar/413: [Revert "Merge pull request #386 ... gpb-recompilation...detection"](https://github.com/rebar/rebar/pull/413) +* rebar/418: [Fix #415 (reltool vsn check)](https://github.com/rebar/rebar/pull/418) +* rebar/420: [Mock gpb and protobuffs, in inttest, replacing external dependencies](https://github.com/rebar/rebar/pull/420) +* rebar/421: [inttest/ct3: fix travis-ci breakage](https://github.com/rebar/rebar/pull/421) +* rebar/424: [Gpb recompilation detection (using base compiler)](https://github.com/rebar/rebar/pull/424) +* rebar/425: [Copy instead of rsync gpb and protobuffs inttest mocks](https://github.com/rebar/rebar/pull/425) +* rebar/426: [Fixed #133. Release upgrade now handle long and short names properly.](https://github.com/rebar/rebar/pull/426) +* rebar/428: [Remove a git url in gpb and protobuffs inttest configs](https://github.com/rebar/rebar/pull/428) +* rebar/430: [Exit with proper status code if 'eval' fails](https://github.com/rebar/rebar/pull/430) +* rebar/432: [Document recursive_cmds in -r help string as well](https://github.com/rebar/rebar/pull/432) +* rebar/433: [inttest/ct3: fix overlong line](https://github.com/rebar/rebar/pull/433) +* rebar/437: [Fix .app.src.script bug introduced in b44b4f4](https://github.com/rebar/rebar/pull/437) +* rebar/440: [Delete obsolete file](https://github.com/rebar/rebar/pull/440) +* rebar/442: [.travis.yml: print information before running script](https://github.com/rebar/rebar/pull/442) +* rebar/444: [Fix incorrect "not an app dir" warning](https://github.com/rebar/rebar/pull/444) +* rebar/445: [rebar doesn't respect the order of erl_first_files given in the rebar.conf file](https://github.com/rebar/rebar/pull/445) +* rebar/447: [Support custom protobuf directory](https://github.com/rebar/rebar/pull/447) +* rebar/449: [Support .appup.src files](https://github.com/rebar/rebar/pull/449) +* rebar/452: [added 'shell' command to bash-completion](https://github.com/rebar/rebar/pull/452) +* rebar/456: [Generate json output from cover](https://github.com/rebar/rebar/pull/456) +* rebar/458: [Change env var delimiter to match non-word](https://github.com/rebar/rebar/pull/458) +* rebar/459: [Remove -m64 flag.](https://github.com/rebar/rebar/pull/459) +* rebar/460: [Add Dialyzer plugin](https://github.com/rebar/rebar/pull/460) +* rebar/461: [Fix OTP .appup.src processing on empty lists](https://github.com/rebar/rebar/pull/461) +* rebar/463: [Print more info when profiling](https://github.com/rebar/rebar/pull/463) +* rebar/466: [Improve test targets in Makefile](https://github.com/rebar/rebar/pull/466) +* rebar/467: [Refactor logic and optimizations in rebar_erlc_compiler:doterl_compile/4](https://github.com/rebar/rebar/pull/467) +* rebar/469: [Fix dialyzer warnings](https://github.com/rebar/rebar/pull/469) +* rebar/470: [Fix whitespace errors](https://github.com/rebar/rebar/pull/470) +* rebar/471: [Fix whitespace errors](https://github.com/rebar/rebar/pull/471) +* rebar/475: [Adapt to 18.x time api changes](https://github.com/rebar/rebar/pull/475) +* rebar/477: [Enable parse transformations in rebar config](https://github.com/rebar/rebar/pull/477) +* rebar/478: [bootstrap: better warning fix (Thanks James Fish)](https://github.com/rebar/rebar/pull/478) +* rebar/482: [Windows runner - CD to node root](https://github.com/rebar/rebar/pull/482) +* rebar/484: [/me added to THANKS](https://github.com/rebar/rebar/pull/484) +* rebar/485: [avoid pre-compile time errors in expand_include_lib_path](https://github.com/rebar/rebar/pull/485) +* rebar/487: [erlc: fix recently introduced whitespace errors](https://github.com/rebar/rebar/pull/487) +* rebar/489: [Fix crash on failed build console output](https://github.com/rebar/rebar/pull/489) +* rebar/494: [Update line number of allowed dialyzer error](https://github.com/rebar/rebar/pull/494) +* rebar/496: [Update PLT detail for make build_plt](https://github.com/rebar/rebar/pull/496) +* rebar/499: [Try one more location for the OTP_VERSION file](https://github.com/rebar/rebar/pull/499) +* rebar/500: [Makefile: clean only .rebar/erlcinfo](https://github.com/rebar/rebar/pull/500) +* rebar/502: [Don't crash if missing OTP_VERSION file (fix #350)](https://github.com/rebar/rebar/pull/502) +* rebar/504: [Drop `shared` PLTs support and change PLT name to .plt](https://github.com/rebar/rebar/pull/504) +* rebar/505: [rebar_utils: explain pdict use](https://github.com/rebar/rebar/pull/505) +* rebar/508: [rebar_core: add missing newline in log msg](https://github.com/rebar/rebar/pull/508) +* rebar/509: [erlc: do not crash if dep file cannot be found](https://github.com/rebar/rebar/pull/509) +* rebar/510: [Fix 'make deps'](https://github.com/rebar/rebar/pull/510) +* rebar/511: [Add and use memoization server](https://github.com/rebar/rebar/pull/511) +* rebar/512: [rmemo: properly handle unsupported call](https://github.com/rebar/rebar/pull/512) + + +# Rebar 2.5.1 + +* rebar/299: [Fix OS X resource fork handling (Reported-by: Richard O'Keefe)](https://github.com/rebar/rebar/pull/299) +* rebar/307: [bootstrap now accepts --help usage flag](https://github.com/rebar/rebar/pull/307) +* rebar/316: [fix for #314 (rebar shell somehow blocks using io:format in gen_server handle_call)](https://github.com/rebar/rebar/pull/316) +* rebar/327: [Adapt arch string to versioning scheme changes (>= 17.x)](https://github.com/rebar/rebar/pull/327) +* rebar/328: [Follow-up typo fixes for #327](https://github.com/rebar/rebar/pull/328) +* rebar/330: [Remove experimental label from 'eunit tests='](https://github.com/rebar/rebar/pull/330) +* rebar/332: [Update dialyzer_reference](https://github.com/rebar/rebar/pull/332) + +# Rebar 2.5.0 + +* Reverted rebar/281: [Move include/rebar.hrl to src/rebar.hrl](https://github.com/rebar/rebar/pull/281) as it broke backwards compatibility + +# Rebar 2.4.0 + +* rebar/52: [Slim release support](https://github.com/rebar/rebar/pull/52) +* rebar/112: [Add code coverage analysis functionality to `qc'](https://github.com/rebar/rebar/pull/112) +* rebar/119: [Add qualified name tests specification (see #118)](https://github.com/rebar/rebar/pull/119) +* rebar/130: [ct fixes](https://github.com/rebar/rebar/pull/130) +* rebar/136: [Add support for the Perforce VCS client via the "p4" tool](https://github.com/rebar/rebar/pull/136) +* rebar/195: [Switch template instructions](https://github.com/rebar/rebar/pull/195) +* rebar/229: [Add REBAR to environment before executing hooks](https://github.com/rebar/rebar/pull/229) +* rebar/260: [Quote include/lib paths to handle spaces in Erlang installs (fixes build on windows)](https://github.com/rebar/rebar/pull/260) +* rebar/280: [improve output when using `rebar shell`](https://github.com/rebar/rebar/pull/280) +* rebar/281: [Move include/rebar.hrl to src/rebar.hrl](https://github.com/rebar/rebar/pull/281) +* rebar/284: [Error 'Command not found' when sname is used](https://github.com/rebar/rebar/pull/284) +* rebar/285: [Fix #249 (erlc regression)](https://github.com/rebar/rebar/pull/285) +* rebar/288: [Extend and document contributing rules](https://github.com/rebar/rebar/pull/288) +* rebar/289: [erlc: fix typo in update_erlcinfo/3 clause that would make the function fail](https://github.com/rebar/rebar/pull/289) +* rebar/290: [erlc: replace if expression with case of](https://github.com/rebar/rebar/pull/290) +* rebar/292: [Namespaced types: fix build for 17.0](https://github.com/rebar/rebar/pull/292) +* rebar/296: [Add gen_event template](https://github.com/rebar/rebar/pull/296) + + +# Rebar 2.3.1 + +## PR's Merged + +* rebar/242: [Extra commits for #129](https://github.com/rebar/rebar/pull/242) +* rebar/244: [Document skip_apps=, apps=, and require_*_vsn](https://github.com/rebar/rebar/pull/244) +* rebar/251: [Make sure that eunit/qc_compile_opts works](https://github.com/rebar/rebar/pull/251) +* rebar/255: [rebar.app: remove superfluous quoting](https://github.com/rebar/rebar/pull/255) +* rebar/274: [Use lowercase for Windows drive name to resolve issue #250](https://github.com/rebar/rebar/pull/274) + +# Rebar 2.3.0 + +## PR's Merged + +* rebar/98: [Repetition of environment variable definitions in child processes (ports)](https://github.com/rebar/rebar/pull/98) +* rebar/115: [Incorrect REMSH args when sname is used.](https://github.com/rebar/rebar/pull/115) +* rebar/129: [Speed up the compilation process v5](https://github.com/rebar/rebar/pull/129) +* rebar/139: [Allow specification of module dependencies for appups](https://github.com/rebar/rebar/pull/139) +* rebar/175: [CWD plugins regression](https://github.com/rebar/rebar/pull/175) +* rebar/188: [Xref extra path](https://github.com/rebar/rebar/pull/188) +* rebar/208: [Fix typo in rebar_erlydtl_compiler](https://github.com/rebar/rebar/pull/208) +* rebar/219: [Added R16B01 and R16B02 to travis config.](https://github.com/rebar/rebar/pull/219) +* rebar/221: [Adapt erlydtl compiler plugin to latest version of erlydtl](https://github.com/rebar/rebar/pull/221) +* rebar/223: [Add random_suite_order option to eunit command](https://github.com/rebar/rebar/pull/223) +* rebar/224: [allow suites or tests as options for eunit and ct](https://github.com/rebar/rebar/pull/224) +* rebar/230: [eunit: fix dialyzer warnings introduced in 03da5e0b](https://github.com/rebar/rebar/pull/230) +* rebar/232: [Document support for abbreviated commands](https://github.com/rebar/rebar/pull/232) +* rebar/233: [docs: fix #228](https://github.com/rebar/rebar/pull/233) +* rebar/234: [Fix #220 (Reported-by: Joseph Norton)](https://github.com/rebar/rebar/pull/234) +* rebar/237: [Add partial support for Erlang/OTP 17](https://github.com/rebar/rebar/pull/237) +* rebar/252: [file_utils: properly report errors (fix #95)](https://github.com/rebar/rebar/pull/252) +* rebar/254: [Fix 'rebar generate' regression (#253)](https://github.com/rebar/rebar/pull/254) +* rebar/265: [Fix 'rebar help clean' function_clause error](https://github.com/rebar/rebar/pull/265) +* rebar/268: [Fix #267 (code path regression)](https://github.com/rebar/rebar/pull/268) +* rebar/269: [Update THANKS](https://github.com/rebar/rebar/pull/269) + +# Rebar 2.2.0 + +## PR's Merged + +* rebar/152: [Fix erl_opts use](https://github.com/rebar/rebar/pull/152) +* rebar/154: [Fix update-deps with certain forms of the {tag, ...} type](https://github.com/rebar/rebar/pull/154) +* rebar/155: [Fixes for #137 and #142](https://github.com/rebar/rebar/pull/155) +* rebar/157: [Don't over-aggressively clean the code path in the presence of lib_dir directives](https://github.com/rebar/rebar/pull/157) +* rebar/172: [Add missing dep examples and fix existing ones](https://github.com/rebar/rebar/pull/172) +* rebar/173: [Fix false reporting of (plain) vsn strings](https://github.com/rebar/rebar/pull/173) +* rebar/174: [rebar_core: fix Dialyzer warning introduced in aa46d85 (#157)](https://github.com/rebar/rebar/pull/174) +* rebar/177: [Delete unused inttest/retest binary](https://github.com/rebar/rebar/pull/177) +* rebar/179: [Make list of commands (for unabbreviation) easier to maintain](https://github.com/rebar/rebar/pull/179) +* rebar/183: [generate-upgrade can now take target_dir argument](https://github.com/rebar/rebar/pull/183) +* rebar/184: [Fix log levels](https://github.com/rebar/rebar/pull/184) +* rebar/185: [Switch retest dep to upstream (dizzyd/retest.git)](https://github.com/rebar/rebar/pull/185) +* rebar/189: [inttest/rgen1: increase retest timeout (30s -> 60s)](https://github.com/rebar/rebar/pull/189) +* rebar/190: [inttest/rgen_1: double the timeout a second time](https://github.com/rebar/rebar/pull/190) +* rebar/191: [Fix #187 (rename getopt and mustache)](https://github.com/rebar/rebar/pull/191) +* rebar/196: [Print a more appropriate message on 'rebar info'](https://github.com/rebar/rebar/pull/196) +* rebar/198: [Clean up rebar.config.script](https://github.com/rebar/rebar/pull/198) +* rebar/199: [rebar_dia_compiler: fix Dialyzer warnings](https://github.com/rebar/rebar/pull/199) +* rebar/200: [bootstrap: avoid trying to run 'debug' command](https://github.com/rebar/rebar/pull/200) +* rebar/201: [Added a library template.](https://github.com/rebar/rebar/pull/201) +* rebar/210: [Fix #205 (erlydtl:compile/3 returns warnings)](https://github.com/rebar/rebar/pull/210) +* rebar/212: [Fix basho/rebar#388](https://github.com/rebar/rebar/pull/212) +* rebar/214: [Document compile_only=true](https://github.com/rebar/rebar/pull/214) +* rebar/215: [Remove experimental flags](https://github.com/rebar/rebar/pull/215) diff -Nru rebar-2.0.0/src/getopt.erl rebar-2.6.0/src/getopt.erl --- rebar-2.0.0/src/getopt.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/getopt.erl 1970-01-01 00:00:00.000000000 +0000 @@ -1,621 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author Juan Jose Comellas -%%% @copyright (C) 2009 Juan Jose Comellas -%%% @doc Parses command line options with a format similar to that of GNU getopt. -%%% @end -%%% -%%% This source file is subject to the New BSD License. You should have received -%%% a copy of the New BSD license with this software. If not, it can be -%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php -%%%------------------------------------------------------------------- --module(getopt). --author('juanjo@comellas.org'). - --export([parse/2, usage/2, usage/3, usage/4]). - --export_type([arg_type/0, - arg_value/0, - arg_spec/0, - simple_option/0, - compound_option/0, - option/0, - option_spec/0]). - --define(TAB_LENGTH, 8). -%% Indentation of the help messages in number of tabs. --define(INDENTATION, 3). - -%% Position of each field in the option specification tuple. --define(OPT_NAME, 1). --define(OPT_SHORT, 2). --define(OPT_LONG, 3). --define(OPT_ARG, 4). --define(OPT_HELP, 5). - --define(IS_OPT_SPEC(Opt), (tuple_size(Opt) =:= ?OPT_HELP)). - - -%% Atom indicating the data type that an argument can be converted to. --type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'. -%% Data type that an argument can be converted to. --type arg_value() :: atom() | binary() | boolean() | float() | integer() | string(). -%% Argument specification. --type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined. -%% Option type and optional default argument. --type simple_option() :: atom(). --type compound_option() :: {atom(), arg_value()}. --type option() :: simple_option() | compound_option(). -%% Command line option specification. --type option_spec() :: { - Name :: atom(), - Short :: char() | undefined, - Long :: string() | undefined, - ArgSpec :: arg_spec(), - Help :: string() | undefined - }. -%% Output streams --type output_stream() :: 'standard_io' | 'standard_error'. - - -%% @doc Parse the command line options and arguments returning a list of tuples -%% and/or atoms using the Erlang convention for sending options to a -%% function. --spec parse([option_spec()], string() | [string()]) -> - {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}. -parse(OptSpecList, CmdLine) -> - try - Args = if - is_integer(hd(CmdLine)) -> - string:tokens(CmdLine, " \t\n"); - true -> - CmdLine - end, - parse(OptSpecList, [], [], 0, Args) - catch - throw: {error, {_Reason, _Data}} = Error -> - Error - end. - - --spec parse([option_spec()], [option()], [string()], integer(), [string()]) -> - {ok, {[option()], [string()]}}. -%% Process the option terminator. -parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) -> - %% Any argument present after the terminator is not considered an option. - {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}}; -%% Process long options. -parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) -> - parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); -%% Process short options. -parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) -> - parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); -%% Process non-option arguments. -parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) -> - case find_non_option_arg(OptSpecList, ArgPos) of - {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) -> - parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos + 1, Tail); - false -> - parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail) - end; -parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) -> - %% Once we have completed gathering the options we add the ones that were - %% not present but had default arguments in the specification. - {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}. - - -%% @doc Parse a long option, add it to the option accumulator and continue -%% parsing the rest of the arguments recursively. -%% A long option can have the following syntax: -%% --foo Single option 'foo', no argument -%% --foo=bar Single option 'foo', argument "bar" -%% --foo bar Single option 'foo', argument "bar" --spec parse_long_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> - {ok, {[option()], [string()]}}. -parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> - case split_assigned_arg(OptArg) of - {Long, Arg} -> - %% Get option that has its argument within the same string - %% separated by an equal ('=') character (e.g. "--port=1000"). - parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg); - - Long -> - case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of - {Name, _Short, Long, undefined, _Help} -> - parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args); - - {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec -> - %% The option argument string is empty, but the option requires - %% an argument, so we look into the next string in the list. - %% e.g ["--port", "1000"] - parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); - false -> - throw({error, {invalid_option, OptStr}}) - end - end. - - -%% @doc Parse an option where the argument is 'assigned' in the same string using -%% the '=' character, add it to the option accumulator and continue parsing the -%% rest of the arguments recursively. This syntax is only valid for long options. --spec parse_long_option_assigned_arg([option_spec()], [option()], [string()], integer(), - [string()], string(), string(), string()) -> - {ok, {[option()], [string()]}}. -parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) -> - case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of - {_Name, _Short, Long, ArgSpec, _Help} = OptSpec -> - case ArgSpec of - undefined -> - throw({error, {invalid_option_arg, OptStr}}); - _ -> - parse(OptSpecList, add_option_with_assigned_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args) - end; - false -> - throw({error, {invalid_option, OptStr}}) - end. - - -%% @doc Split an option string that may contain an option with its argument -%% separated by an equal ('=') character (e.g. "port=1000"). --spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string(). -split_assigned_arg(OptStr) -> - split_assigned_arg(OptStr, OptStr, []). - -split_assigned_arg(_OptStr, "=" ++ Tail, Acc) -> - {lists:reverse(Acc), Tail}; -split_assigned_arg(OptStr, [Char | Tail], Acc) -> - split_assigned_arg(OptStr, Tail, [Char | Acc]); -split_assigned_arg(OptStr, [], _Acc) -> - OptStr. - - -%% @doc Retrieve the argument for an option from the next string in the list of -%% command-line parameters or set the value of the argument from the argument -%% specification (for boolean and integer arguments), if possible. -parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) -> - ArgSpecType = arg_spec_type(ArgSpec), - case Args =:= [] orelse is_implicit_arg(ArgSpecType, hd(Args)) of - true -> - parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); - false -> - [Arg | Tail] = Args, - try - parse(OptSpecList, [{Name, to_type(ArgSpecType, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) - catch - error:_ -> - throw({error, {invalid_option_arg, {Name, Arg}}}) - end - end. - - -%% @doc Parse a short option, add it to the option accumulator and continue -%% parsing the rest of the arguments recursively. -%% A short option can have the following syntax: -%% -a Single option 'a', no argument or implicit boolean argument -%% -a foo Single option 'a', argument "foo" -%% -afoo Single option 'a', argument "foo" -%% -abc Multiple options: 'a'; 'b'; 'c' -%% -bcafoo Multiple options: 'b'; 'c'; 'a' with argument "foo" -%% -aaa Multiple repetitions of option 'a' (only valid for options with integer arguments) --spec parse_short_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> - {ok, {[option()], [string()]}}. -parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> - parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg). - -parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptPos, [Short | Arg]) -> - case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of - {Name, Short, _Long, undefined, _Help} -> - parse_short_option(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, first, Arg); - - {_Name, Short, _Long, ArgSpec, _Help} = OptSpec -> - %% The option has a specification, so it requires an argument. - case Arg of - [] -> - %% The option argument string is empty, but the option requires - %% an argument, so we look into the next string in the list. - parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec, OptPos); - - _ -> - case is_valid_arg(ArgSpec, Arg) of - true -> - parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args); - _ -> - NewOptAcc = case OptPos of - first -> add_option_with_implicit_arg(OptSpec, OptAcc); - _ -> add_option_with_implicit_incrementable_arg(OptSpec, OptAcc) - end, - parse_short_option(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Args, OptStr, next, Arg) - end - end; - - false -> - throw({error, {invalid_option, OptStr}}) - end; -parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, _OptPos, []) -> - parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args). - - -%% @doc Retrieve the argument for an option from the next string in the list of -%% command-line parameters or set the value of the argument from the argument -%% specification (for boolean and integer arguments), if possible. -parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptPos) -> - case Args =:= [] orelse is_implicit_arg(ArgSpec, hd(Args)) of - true when OptPos =:= first -> - parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); - true -> - parse(OptSpecList, add_option_with_implicit_incrementable_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); - false -> - [Arg | Tail] = Args, - try - parse(OptSpecList, [{Name, to_type(ArgSpec, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) - catch - error:_ -> - throw({error, {invalid_option_arg, {Name, Arg}}}) - end - end. - - -%% @doc Find the option for the discrete argument in position specified in the -%% Pos argument. --spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false. -find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} = OptSpec | _Tail], 0) -> - {value, OptSpec}; -find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} | Tail], Pos) -> - find_non_option_arg(Tail, Pos - 1); -find_non_option_arg([_Head | Tail], Pos) -> - find_non_option_arg(Tail, Pos); -find_non_option_arg([], _Pos) -> - false. - - -%% @doc Append options that were not present in the command line arguments with -%% their default arguments. --spec append_default_options([option_spec()], [option()]) -> [option()]. -append_default_options([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) -> - append_default_options(Tail, - case lists:keymember(Name, 1, OptAcc) of - false -> - [{Name, DefaultArg} | OptAcc]; - _ -> - OptAcc - end); -%% For options with no default argument. -append_default_options([_Head | Tail], OptAcc) -> - append_default_options(Tail, OptAcc); -append_default_options([], OptAcc) -> - OptAcc. - - -%% @doc Add an option with argument converting it to the data type indicated by the -%% argument specification. --spec add_option_with_arg(option_spec(), string(), [option()]) -> [option()]. -add_option_with_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) -> - case is_valid_arg(ArgSpec, Arg) of - true -> - try - [{Name, to_type(ArgSpec, Arg)} | OptAcc] - catch - error:_ -> - throw({error, {invalid_option_arg, {Name, Arg}}}) - end; - false -> - add_option_with_implicit_arg(OptSpec, OptAcc) - end. - - -%% @doc Add an option with argument that was part of an assignment expression -%% (e.g. "--verbose=3") converting it to the data type indicated by the -%% argument specification. --spec add_option_with_assigned_arg(option_spec(), string(), [option()]) -> [option()]. -add_option_with_assigned_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg, OptAcc) -> - try - [{Name, to_type(ArgSpec, Arg)} | OptAcc] - catch - error:_ -> - throw({error, {invalid_option_arg, {Name, Arg}}}) - end. - - -%% @doc Add an option that required an argument but did not have one. Some data -%% types (boolean, integer) allow implicit or assumed arguments. --spec add_option_with_implicit_arg(option_spec(), [option()]) -> [option()]. -add_option_with_implicit_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> - case arg_spec_type(ArgSpec) of - boolean -> - %% Special case for boolean arguments: if there is no argument we - %% set the value to 'true'. - [{Name, true} | OptAcc]; - integer -> - %% Special case for integer arguments: if the option had not been set - %% before we set the value to 1. This is needed to support options like - %% "-v" to return something like {verbose, 1}. - [{Name, 1} | OptAcc]; - _ -> - throw({error, {missing_option_arg, Name}}) - end. - - -%% @doc Add an option with an implicit or assumed argument. --spec add_option_with_implicit_incrementable_arg(option_spec() | arg_spec(), [option()]) -> [option()]. -add_option_with_implicit_incrementable_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> - case arg_spec_type(ArgSpec) of - boolean -> - %% Special case for boolean arguments: if there is no argument we - %% set the value to 'true'. - [{Name, true} | OptAcc]; - integer -> - %% Special case for integer arguments: if the option had not been set - %% before we set the value to 1; if not we increment the previous value - %% the option had. This is needed to support options like "-vvv" to - %% return something like {verbose, 3}. - case OptAcc of - [{Name, Count} | Tail] -> - [{Name, Count + 1} | Tail]; - _ -> - [{Name, 1} | OptAcc] - end; - _ -> - throw({error, {missing_option_arg, Name}}) - end. - - -%% @doc Retrieve the data type form an argument specification. --spec arg_spec_type(arg_spec()) -> arg_type() | undefined. -arg_spec_type({Type, _DefaultArg}) -> - Type; -arg_spec_type(Type) when is_atom(Type) -> - Type. - - -%% @doc Convert an argument string to its corresponding data type. --spec to_type(arg_spec() | arg_type(), string()) -> arg_value(). -to_type({Type, _DefaultArg}, Arg) -> - to_type(Type, Arg); -to_type(binary, Arg) -> - list_to_binary(Arg); -to_type(atom, Arg) -> - list_to_atom(Arg); -to_type(integer, Arg) -> - list_to_integer(Arg); -to_type(float, Arg) -> - list_to_float(Arg); -to_type(boolean, Arg) -> - LowerArg = string:to_lower(Arg), - case is_arg_true(LowerArg) of - true -> - true; - _ -> - case is_arg_false(LowerArg) of - true -> - false; - false -> - erlang:error(badarg) - end - end; -to_type(_Type, Arg) -> - Arg. - - --spec is_arg_true(string()) -> boolean(). -is_arg_true(Arg) -> - (Arg =:= "true") orelse (Arg =:= "t") orelse - (Arg =:= "yes") orelse (Arg =:= "y") orelse - (Arg =:= "on") orelse (Arg =:= "enabled") orelse - (Arg =:= "1"). - - --spec is_arg_false(string()) -> boolean(). -is_arg_false(Arg) -> - (Arg =:= "false") orelse (Arg =:= "f") orelse - (Arg =:= "no") orelse (Arg =:= "n") orelse - (Arg =:= "off") orelse (Arg =:= "disabled") orelse - (Arg =:= "0"). - - --spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean(). -is_valid_arg({Type, _DefaultArg}, Arg) -> - is_valid_arg(Type, Arg); -is_valid_arg(boolean, Arg) -> - is_boolean_arg(Arg); -is_valid_arg(integer, Arg) -> - is_non_neg_integer_arg(Arg); -is_valid_arg(float, Arg) -> - is_non_neg_float_arg(Arg); -is_valid_arg(_Type, _Arg) -> - true. - - --spec is_implicit_arg(arg_spec(), nonempty_string()) -> boolean(). -is_implicit_arg({Type, _DefaultArg}, Arg) -> - is_implicit_arg(Type, Arg); -is_implicit_arg(boolean, Arg) -> - not is_boolean_arg(Arg); -is_implicit_arg(integer, Arg) -> - not is_integer_arg(Arg); -is_implicit_arg(_Type, _Arg) -> - false. - - --spec is_boolean_arg(string()) -> boolean(). -is_boolean_arg(Arg) -> - LowerArg = string:to_lower(Arg), - is_arg_true(LowerArg) orelse is_arg_false(LowerArg). - - --spec is_integer_arg(string()) -> boolean(). -is_integer_arg("-" ++ Tail) -> - is_non_neg_integer_arg(Tail); -is_integer_arg(Arg) -> - is_non_neg_integer_arg(Arg). - - --spec is_non_neg_integer_arg(string()) -> boolean(). -is_non_neg_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 -> - is_non_neg_integer_arg(Tail); -is_non_neg_integer_arg([_Head | _Tail]) -> - false; -is_non_neg_integer_arg([]) -> - true. - - --spec is_non_neg_float_arg(string()) -> boolean(). -is_non_neg_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. -> - is_non_neg_float_arg(Tail); -is_non_neg_float_arg([_Head | _Tail]) -> - false; -is_non_neg_float_arg([]) -> - true. - - -%% @doc Show a message on standard_error indicating the command line options and -%% arguments that are supported by the program. --spec usage([option_spec()], string()) -> ok. -usage(OptSpecList, ProgramName) -> - usage(OptSpecList, ProgramName, standard_error). - - -%% @doc Show a message on standard_error or standard_io indicating the command line options and -%% arguments that are supported by the program. --spec usage([option_spec()], string(), output_stream() | string()) -> ok. -usage(OptSpecList, ProgramName, OutputStream) when is_atom(OutputStream) -> - io:format(OutputStream, "Usage: ~s~s~n~n~s~n", - [ProgramName, usage_cmd_line(OptSpecList), usage_options(OptSpecList)]); -%% @doc Show a message on standard_error indicating the command line options and -%% arguments that are supported by the program. The CmdLineTail argument -%% is a string that is added to the end of the usage command line. -usage(OptSpecList, ProgramName, CmdLineTail) -> - usage(OptSpecList, ProgramName, CmdLineTail, standard_error). - - -%% @doc Show a message on standard_error or standard_io indicating the command line options and -%% arguments that are supported by the program. The CmdLineTail argument -%% is a string that is added to the end of the usage command line. --spec usage([option_spec()], string(), string(), output_stream() | [{string(), string()}]) -> ok. -usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) -> - io:format(OutputStream, "Usage: ~s~s ~s~n~n~s~n", - [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, usage_options(OptSpecList)]); -%% @doc Show a message on standard_error indicating the command line options and -%% arguments that are supported by the program. The CmdLineTail and OptionsTail -%% arguments are a string that is added to the end of the usage command line -%% and a list of tuples that are added to the end of the options' help lines. -usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) -> - usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error). - - -%% @doc Show a message on standard_error or standard_io indicating the command line options and -%% arguments that are supported by the program. The CmdLineTail and OptionsTail -%% arguments are a string that is added to the end of the usage command line -%% and a list of tuples that are added to the end of the options' help lines. --spec usage([option_spec()], string(), string(), [{string(), string()}], output_stream()) -> ok. -usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, OutputStream) -> - UsageOptions = lists:foldl( - fun ({Prefix, Help}, Acc) -> - add_option_help(Prefix, Help, Acc) - end, usage_options_reverse(OptSpecList, []), OptionsTail), - io:format(OutputStream, "Usage: ~s~s ~s~n~n~s~n", - [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, - lists:flatten(lists:reverse(UsageOptions))]). - - -%% @doc Return a string with the syntax for the command line options and -%% arguments. --spec usage_cmd_line([option_spec()]) -> string(). -usage_cmd_line(OptSpecList) -> - usage_cmd_line(OptSpecList, []). - -usage_cmd_line([{Name, Short, Long, ArgSpec, _Help} | Tail], Acc) -> - CmdLine = - case ArgSpec of - undefined -> - if - %% For options with short form and no argument. - Short =/= undefined -> - [$\s, $[, $-, Short, $]]; - %% For options with only long form and no argument. - Long =/= undefined -> - [$\s, $[, $-, $-, Long, $]]; - true -> - [] - end; - _ -> - if - %% For options with short form and argument. - Short =/= undefined -> - [$\s, $[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]]; - %% For options with only long form and argument. - Long =/= undefined -> - [$\s, $[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]]; - %% For options with neither short nor long form and argument. - true -> - [$\s, $<, atom_to_list(Name), $>] - end - end, - usage_cmd_line(Tail, [CmdLine | Acc]); -usage_cmd_line([], Acc) -> - lists:flatten(lists:reverse(Acc)). - - -%% @doc Return a string with the help message for each of the options and -%% arguments. --spec usage_options([option_spec()]) -> string(). -usage_options(OptSpecList) -> - lists:flatten(lists:reverse(usage_options_reverse(OptSpecList, []))). - -usage_options_reverse([{Name, Short, Long, _ArgSpec, Help} | Tail], Acc) -> - Prefix = - case Long of - undefined -> - case Short of - %% Neither short nor long form (non-option argument). - undefined -> - [$<, atom_to_list(Name), $>]; - %% Only short form. - _ -> - [$-, Short] - end; - _ -> - case Short of - %% Only long form. - undefined -> - [$-, $- | Long]; - %% Both short and long form. - _ -> - [$-, Short, $,, $\s, $-, $- | Long] - end - end, - usage_options_reverse(Tail, add_option_help(Prefix, Help, Acc)); -usage_options_reverse([], Acc) -> - Acc. - - -%% @doc Add the help message corresponding to an option specification to a list -%% with the correct indentation. --spec add_option_help(Prefix :: string(), Help :: string(), Acc :: string()) -> string(). -add_option_help(Prefix, Help, Acc) when is_list(Help), Help =/= [] -> - FlatPrefix = lists:flatten(Prefix), - case ((?INDENTATION * ?TAB_LENGTH) - 2 - length(FlatPrefix)) of - TabSize when TabSize > 0 -> - Tab = lists:duplicate(ceiling(TabSize / ?TAB_LENGTH), $\t), - [[$\s, $\s, FlatPrefix, Tab, Help, $\n] | Acc]; - _ -> - % The indentation for the option description is 3 tabs (i.e. 24 characters) - % IMPORTANT: Change the number of tabs below if you change the - % value of the INDENTATION macro. - [[$\t, $\t, $\t, Help, $\n], [$\s, $\s, FlatPrefix, $\n] | Acc] - end; -add_option_help(_Opt, _Prefix, Acc) -> - Acc. - - - -%% @doc Return the smallest integral value not less than the argument. --spec ceiling(float()) -> integer(). -ceiling(X) -> - T = erlang:trunc(X), - case (X - T) of - % Neg when Neg < 0 -> - % T; - Pos when Pos > 0 -> - T + 1; - _ -> - T - end. diff -Nru rebar-2.0.0/src/mustache.erl rebar-2.6.0/src/mustache.erl --- rebar-2.0.0/src/mustache.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/mustache.erl 1970-01-01 00:00:00.000000000 +0000 @@ -1,234 +0,0 @@ -%% The MIT License -%% -%% Copyright (c) 2009 Tom Preston-Werner -%% -%% 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. - -%% See the README at http://github.com/mojombo/mustache.erl for additional -%% documentation and usage examples. - --module(mustache). %% v0.1.0 --author("Tom Preston-Werner"). --export([compile/1, compile/2, render/1, render/2, render/3, get/2, get/3, escape/1, start/1]). - --record(mstate, {mod = undefined, - 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), - % io:format("~p~n~n", [CompiledTemplate]), - % io:format(CompiledTemplate ++ "~n", []), - {ok, Tokens, _} = erl_scan:string(CompiledTemplate), - {ok, [Form]} = erl_parse:parse_exprs(Tokens), - Bindings = erl_eval:new_bindings(), - {value, Fun, _} = erl_eval:expr(Form, Bindings), - Fun; -compile(Mod) -> - TemplatePath = template_path(Mod), - compile(Mod, TemplatePath). - -compile(Mod, File) -> - code:purge(Mod), - {module, _} = code:load_file(Mod), - {ok, TemplateBin} = file:read_file(File), - Template = re:replace(TemplateBin, "\"", "\\\\\"", [global, {return,list}]), - State = #mstate{mod = Mod}, - CompiledTemplate = pre_compile(Template, State), - % io:format("~p~n~n", [CompiledTemplate]), - % io:format(CompiledTemplate ++ "~n", []), - {ok, Tokens, _} = erl_scan:string(CompiledTemplate), - {ok, [Form]} = erl_parse:parse_exprs(Tokens), - Bindings = erl_eval:new_bindings(), - {value, Fun, _} = erl_eval:expr(Form, Bindings), - Fun. - -render(Mod) -> - TemplatePath = template_path(Mod), - render(Mod, TemplatePath). - -render(Body, Ctx) when is_list(Body) -> - TFun = compile(Body), - render(undefined, TFun, Ctx); -render(Mod, File) when is_list(File) -> - render(Mod, File, dict:new()); -render(Mod, CompiledTemplate) -> - render(Mod, CompiledTemplate, dict:new()). - -render(Mod, File, Ctx) when is_list(File) -> - CompiledTemplate = compile(Mod, File), - render(Mod, CompiledTemplate, Ctx); -render(Mod, CompiledTemplate, Ctx) -> - Ctx2 = dict:store('__mod__', Mod, Ctx), - lists:flatten(CompiledTemplate(Ctx2)). - -pre_compile(T, State) -> - SectionRE = "\{\{\#([^\}]*)}}\s*(.+?){{\/\\1\}\}\s*", - {ok, CompiledSectionRE} = re:compile(SectionRE, [dotall]), - TagRE = "\{\{(#|=|!|<|>|\{)?(.+?)\\1?\}\}+", - {ok, CompiledTagRE} = re:compile(TagRE, [dotall]), - State2 = State#mstate{section_re = CompiledSectionRE, tag_re = CompiledTagRE}, - "fun(Ctx) -> " ++ - "CFun = fun(A, B) -> A end, " ++ - compiler(T, State2) ++ " end.". - -compiler(T, State) -> - Res = re:run(T, State#mstate.section_re), - case Res of - {match, [{M0, M1}, {N0, N1}, {C0, C1}]} -> - Front = string:substr(T, 1, M0), - Back = string:substr(T, M0 + M1 + 1), - Name = string:substr(T, N0 + 1, N1), - Content = string:substr(T, C0 + 1, C1), - "[" ++ compile_tags(Front, State) ++ - " | [" ++ compile_section(Name, Content, State) ++ - " | [" ++ compiler(Back, State) ++ "]]]"; - nomatch -> - compile_tags(T, State) - end. - -compile_section(Name, Content, State) -> - Mod = State#mstate.mod, - Result = compiler(Content, State), - "fun() -> " ++ - "case mustache:get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++ - "true -> " ++ - Result ++ "; " ++ - "false -> " ++ - "[]; " ++ - "List when is_list(List) -> " ++ - "[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++ - "Else -> " ++ - "throw({template, io_lib:format(\"Bad context for ~p: ~p\", [" ++ Name ++ ", Else])}) " ++ - "end " ++ - "end()". - -compile_tags(T, State) -> - Res = re:run(T, State#mstate.tag_re), - case Res of - {match, [{M0, M1}, K, {C0, C1}]} -> - Front = string:substr(T, 1, M0), - Back = string:substr(T, M0 + M1 + 1), - Content = string:substr(T, C0 + 1, C1), - Kind = tag_kind(T, K), - Result = compile_tag(Kind, Content, State), - "[\"" ++ Front ++ - "\" | [" ++ Result ++ - " | " ++ compile_tags(Back, State) ++ "]]"; - nomatch -> - "[\"" ++ T ++ "\"]" - end. - -tag_kind(_T, {-1, 0}) -> - none; -tag_kind(T, {K0, K1}) -> - string:substr(T, K0 + 1, K1). - -compile_tag(none, Content, State) -> - Mod = State#mstate.mod, - "mustache:escape(mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))"; -compile_tag("{", Content, State) -> - Mod = State#mstate.mod, - "mustache:get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")"; -compile_tag("!", _Content, _State) -> - "[]". - -template_path(Mod) -> - ModPath = code:which(Mod), - re:replace(ModPath, "\.beam$", ".mustache", [{return, list}]). - -get(Key, Ctx) when is_list(Key) -> - {ok, Mod} = dict:find('__mod__', Ctx), - get(list_to_atom(Key), Ctx, Mod); -get(Key, Ctx) -> - {ok, Mod} = dict:find('__mod__', Ctx), - get(Key, Ctx, Mod). - -get(Key, Ctx, Mod) when is_list(Key) -> - get(list_to_atom(Key), Ctx, Mod); -get(Key, Ctx, Mod) -> - case dict:find(Key, Ctx) of - {ok, Val} -> - % io:format("From Ctx {~p, ~p}~n", [Key, Val]), - to_s(Val); - error -> - case erlang:function_exported(Mod, Key, 1) of - true -> - Val = to_s(Mod:Key(Ctx)), - % io:format("From Mod/1 {~p, ~p}~n", [Key, Val]), - Val; - false -> - case erlang:function_exported(Mod, Key, 0) of - true -> - Val = to_s(Mod:Key()), - % io:format("From Mod/0 {~p, ~p}~n", [Key, Val]), - Val; - false -> - [] - end - end - end. - -to_s(Val) when is_integer(Val) -> - integer_to_list(Val); -to_s(Val) when is_float(Val) -> - io_lib:format("~.2f", [Val]); -to_s(Val) when is_atom(Val) -> - atom_to_list(Val); -to_s(Val) -> - Val. - -escape(HTML) -> - escape(HTML, []). - -escape([], Acc) -> - lists:reverse(Acc); -escape(["<" | Rest], Acc) -> - escape(Rest, lists:reverse("<", Acc)); -escape([">" | Rest], Acc) -> - escape(Rest, lists:reverse(">", Acc)); -escape(["&" | Rest], Acc) -> - escape(Rest, lists:reverse("&", Acc)); -escape([X | Rest], Acc) -> - escape(Rest, [X | 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 -Nru rebar-2.0.0/src/rebar_abnfc_compiler.erl rebar-2.6.0/src/rebar_abnfc_compiler.erl --- rebar-2.0.0/src/rebar_abnfc_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_abnfc_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -47,6 +47,9 @@ -export([compile/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -62,11 +65,23 @@ option(module_ext, DtlOpts) ++ ".erl", fun compile_abnfc/3). - %% =================================================================== %% Internal functions %% =================================================================== +info(help, compile) -> + ?CONSOLE( + "Build ABNF (*.abnf) sources.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + {abnfc_opts, [{doc_root, "src"}, + {out_dir, "src"}, + {source_ext, ".abnfc"}, + {module_ext, ""}]} + ]). + abnfc_opts(Config) -> rebar_config:get(Config, abnfc_opts, []). diff -Nru rebar-2.0.0/src/rebar_appups.erl rebar-2.6.0/src/rebar_appups.erl --- rebar-2.0.0/src/rebar_appups.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_appups.erl 2015-06-19 16:14:28.000000000 +0000 @@ -31,6 +31,9 @@ -export(['generate-appups'/2]). +%% for internal use only +-export([info/2]). + -define(APPUPFILEFORMAT, "%% appup generated for ~p by rebar (~p)~n" "{~p, [{~p, ~p}], [{~p, []}]}.~n"). @@ -38,17 +41,20 @@ %% 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), + + PrevRelPath = rebar_rel_utils:get_previous_release_path(Config), + OldVerPath = filename:join([TargetParentDir, PrevRelPath]), - OldVerPath = filename:join([TargetParentDir, - rebar_rel_utils:get_previous_release_path()]), + ModDeps = rebar_config:get(Config, module_deps, []), %% Get the new and old release name and versions {Name, _Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - NewVerPath = filename:join([TargetParentDir, Name]), + NewVerPath = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath), {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), @@ -63,8 +69,8 @@ {_Added, _Removed, Upgraded} = get_apps(Name, OldVerPath, NewVerPath), %% Get a list of any appup files that exist in the new release - NewAppUpFiles = rebar_utils:find_files( - filename:join([NewVerPath, "lib"]), "^.*.appup$"), + NewAppUpFiles = rebar_utils:find_files_by_ext( + filename:join([NewVerPath, "lib"]), ".appup"), %% Convert the list of appup files into app names AppUpApps = [file_to_name(File) || File <- NewAppUpFiles], @@ -73,14 +79,21 @@ UpgradeApps = genappup_which_apps(Upgraded, AppUpApps), %% Generate appup files for upgraded apps - generate_appup_files(NewVerPath, OldVerPath, UpgradeApps), + generate_appup_files(NewVerPath, OldVerPath, ModDeps, UpgradeApps), - ok. + {ok, Config1}. %% =================================================================== %% Internal functions %% =================================================================== +info(help, 'generate-appups') -> + ?CONSOLE("Generate appup files.~n" + "~n" + "Valid command line options:~n" + " previous_release=path~n", + []). + get_apps(Name, OldVerPath, NewVerPath) -> OldApps = rebar_rel_utils:get_rel_apps(Name, OldVerPath), ?DEBUG("Old Version Apps: ~p~n", [OldApps]), @@ -128,9 +141,9 @@ genappup_which_apps(Apps, []) -> Apps. -generate_appup_files(NewVerPath, OldVerPath, [{_App, {undefined, _}}|Rest]) -> - generate_appup_files(NewVerPath, OldVerPath, Rest); -generate_appup_files(NewVerPath, OldVerPath, [{App, {OldVer, NewVer}}|Rest]) -> +generate_appup_files(NewVerPath, OldVerPath, ModDeps, [{_App, {undefined, _}}|Rest]) -> + generate_appup_files(NewVerPath, OldVerPath, ModDeps, Rest); +generate_appup_files(NewVerPath, OldVerPath, ModDeps, [{App, {OldVer, NewVer}}|Rest]) -> OldEbinDir = filename:join([OldVerPath, "lib", atom_to_list(App) ++ "-" ++ OldVer, "ebin"]), NewEbinDir = filename:join([NewVerPath, "lib", @@ -139,9 +152,14 @@ {AddedFiles, DeletedFiles, ChangedFiles} = beam_lib:cmp_dirs(NewEbinDir, OldEbinDir), + ChangedNames = [list_to_atom(file_to_name(F)) || {F, _} <- ChangedFiles], + ModDeps1 = [{N, [M1 || M1 <- M, lists:member(M1, ChangedNames)]} + || {N, M} <- ModDeps], + Added = [generate_instruction(added, File) || File <- AddedFiles], Deleted = [generate_instruction(deleted, File) || File <- DeletedFiles], - Changed = [generate_instruction(changed, File) || File <- ChangedFiles], + Changed = [generate_instruction(changed, ModDeps1, File) + || File <- ChangedFiles], Inst = lists:append([Added, Deleted, Changed]), @@ -153,8 +171,8 @@ OldVer, Inst, OldVer])), ?CONSOLE("Generated appup for ~p~n", [App]), - generate_appup_files(NewVerPath, OldVerPath, Rest); -generate_appup_files(_, _, []) -> + generate_appup_files(NewVerPath, OldVerPath, ModDeps, Rest); +generate_appup_files(_, _, _, []) -> ?CONSOLE("Appup generation complete~n", []). generate_instruction(added, File) -> @@ -162,25 +180,27 @@ {add_module, Name}; generate_instruction(deleted, File) -> Name = list_to_atom(file_to_name(File)), - {delete_module, Name}; -generate_instruction(changed, {File, _}) -> + {delete_module, Name}. + +generate_instruction(changed, ModDeps, {File, _}) -> {ok, {Name, List}} = beam_lib:chunks(File, [attributes, exports]), Behavior = get_behavior(List), CodeChange = is_code_change(List), - generate_instruction_advanced(Name, Behavior, CodeChange). + Deps = proplists:get_value(Name, ModDeps, []), + generate_instruction_advanced(Name, Behavior, CodeChange, Deps). -generate_instruction_advanced(Name, undefined, undefined) -> +generate_instruction_advanced(Name, undefined, undefined, Deps) -> %% Not a behavior or code change, assume purely functional - {load_module, Name}; -generate_instruction_advanced(Name, [supervisor], _) -> + {load_module, Name, Deps}; +generate_instruction_advanced(Name, [supervisor], _, _) -> %% Supervisor {update, Name, supervisor}; -generate_instruction_advanced(Name, _, code_change) -> +generate_instruction_advanced(Name, _, code_change, Deps) -> %% Includes code_change export - {update, Name, {advanced, []}}; -generate_instruction_advanced(Name, _, _) -> + {update, Name, {advanced, []}, Deps}; +generate_instruction_advanced(Name, _, _, Deps) -> %% Anything else - {load_module, Name}. + {load_module, Name, Deps}. get_behavior(List) -> Attributes = proplists:get_value(attributes, List), diff -Nru rebar-2.0.0/src/rebar_app_utils.erl rebar-2.6.0/src/rebar_app_utils.erl --- rebar-2.0.0/src/rebar_app_utils.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_app_utils.erl 2015-06-19 16:14:28.000000000 +0000 @@ -26,15 +26,16 @@ %% ------------------------------------------------------------------- -module(rebar_app_utils). --export([is_app_dir/0, is_app_dir/1, +-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"). @@ -46,106 +47,140 @@ is_app_dir(rebar_utils:get_cwd()). is_app_dir(Dir) -> - SrcDir = filename:join([Dir, "src"]), - AppSrc = filename:join([SrcDir, "*.app.src"]), - case filelib:wildcard(AppSrc) of - [AppSrcFile] -> - {true, AppSrcFile}; - [] -> - EbinDir = filename:join([Dir, "ebin"]), - App = filename:join([EbinDir, "*.app"]), - case filelib:wildcard(App) of - [AppFile] -> - {true, AppFile}; - [] -> - false; - _ -> - ?ERROR("More than one .app file in ~s~n", [EbinDir]), - false - end; - _ -> - ?ERROR("More than one .app.src file in ~s~n", [SrcDir]), - false - end. + SrcDir = filename:join([Dir, "src"]), + AppSrcScript = filename:join([SrcDir, "*.app.src.script"]), + AppSrc = filename:join([SrcDir, "*.app.src"]), + case {filelib:wildcard(AppSrcScript), filelib:wildcard(AppSrc)} of + {[AppSrcScriptFile], _} -> + {true, AppSrcScriptFile}; + {[], [AppSrcFile]} -> + {true, AppSrcFile}; + {[],[]} -> + EbinDir = filename:join([Dir, "ebin"]), + App = filename:join([EbinDir, "*.app"]), + case filelib:wildcard(App) of + [AppFile] -> + {true, AppFile}; + [] -> + false; + _ -> + ?ERROR("More than one .app file in ~s~n", [EbinDir]), + false + end; + {_, _} -> + ?ERROR("More than one .app.src file in ~s~n", [SrcDir]), + false + end. is_app_src(Filename) -> %% If removing the extension .app.src yields a shorter name, %% this is an .app.src file. - Filename =/= filename:rootname(Filename, ".app.src"). + Filename =/= filename:rootname(Filename, ".app.src") orelse + Filename =/= filename:rootname(Filename, ".app.src.script"). 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; + Filebase = case filename:rootname(Filename, ".app.src") of + Filename -> + filename:basename(Filename, ".app.src.script"); + _ -> + filename:basename(Filename, ".app.src") + end, + filename:join("ebin", Filebase ++ ".app"). + +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 - {ok, [{application, AppName, AppData}]} -> - erlang:put(AppFile, {AppName, AppData}), - {ok, AppName, AppData}; + case consult_app_file(Filename) of + {ok, {application, AppName, AppData}} -> + Config1 = rebar_config:set_xconf(Config, + {appfile, AppFile}, + {AppName, AppData}), + {ok, Config1, AppName, AppData}; {error, _} = Error -> - Error; + {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) -> + Result = case lists:suffix(".app", Filename) of + true -> + file:consult(Filename); + false -> + rebar_config:consult_file(Filename) + end, + case Result of + {ok, [Term]} -> + {ok, Term}; + _ -> + Result end. get_value(Key, AppInfo, AppFile) -> @@ -157,7 +192,7 @@ 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 +201,7 @@ 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 +209,8 @@ {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 -Nru rebar-2.0.0/src/rebar_asn1_compiler.erl rebar-2.6.0/src/rebar_asn1_compiler.erl --- rebar-2.0.0/src/rebar_asn1_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_asn1_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -30,24 +30,44 @@ -export([compile/2, clean/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== %% 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), ok. +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, compile) -> + info_help("Build ASN.1 (*.asn1) sources"); +info(help, clean) -> + info_help("Delete ASN.1 (*.asn1) results"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " {asn1_opts, []} (see asn1ct:compile/2 documentation)~n", + [Description]). + -spec compile_asn1(file:filename(), file:filename(), rebar_config:config()) -> ok. compile_asn1(Source, Target, Config) -> diff -Nru rebar-2.0.0/src/rebar_base_compiler.erl rebar-2.6.0/src/rebar_base_compiler.erl --- rebar-2.0.0/src/rebar_base_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_base_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -28,8 +28,12 @@ -include("rebar.hrl"). --export([run/4, run/7, run/8]). - +-export([run/4, + run/7, + run/8, + run/5, + ok_tuple/3, + error_tuple/5]). %% =================================================================== %% Public API @@ -46,10 +50,10 @@ _ -> 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) + compile_queue(Config, Pids, RestFiles) end. run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, @@ -59,26 +63,40 @@ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, Compile3Fn, Opts) -> - %% Convert simple extension to proper regex - SourceExtRe = ".*\\" ++ SourceExt ++ [$$], %% Find all possible source files - FoundFiles = rebar_utils:find_files(SourceDir, SourceExtRe), + Recursive = proplists:get_value(recursive, Opts, true), + FoundFiles = rebar_utils:find_files_by_ext(SourceDir, SourceExt, Recursive), %% Remove first files from found files RestFiles = [Source || Source <- FoundFiles, not lists:member(Source, FirstFiles)], + FirstUnits = source_to_unit_each(FirstFiles, + SourceDir, SourceExt, + TargetDir, TargetExt), + RestUnits = source_to_unit_each(RestFiles, + SourceDir, SourceExt, + TargetDir, TargetExt), + run(Config, FirstUnits, RestUnits, Compile3Fn, Opts). + +%% FirstUnits and RestUnits are lists of tuples: {Source,Target} +run(Config, FirstUnits, RestUnits, Compile3Fn, Opts) -> + %% Check opts for flag indicating that compile should check lastmod CheckLastMod = proplists:get_bool(check_last_mod, Opts), - run(Config, FirstFiles, RestFiles, - fun(S, C) -> - Target = target_file(S, SourceDir, SourceExt, - TargetDir, TargetExt), + run(Config, FirstUnits, RestUnits, + fun({S, Target}, C) -> simple_compile_wrapper(S, Target, Compile3Fn, C, CheckLastMod) end). +ok_tuple(Config, Source, Ws) -> + {ok, format_warnings(Config, Source, Ws)}. + +error_tuple(Config, Source, Es, Ws, Opts) -> + {error, format_errors(Config, Source, Es), + format_warnings(Config, Source, Ws, Opts)}. %% =================================================================== %% Internal functions @@ -94,12 +112,13 @@ skipped end. +source_to_unit_each(Files, SourceDir, SourceExt, TargetDir, TargetExt) -> + [{File, target_file(File, SourceDir, SourceExt, TargetDir, TargetExt)} + || File <- Files]. + target_file(SourceFile, SourceDir, SourceExt, TargetDir, TargetExt) -> - %% Remove all leading components of the source dir from the file -- we want - %% to maintain the deeper structure (if any) of the source file path BaseFile = remove_common_path(SourceFile, SourceDir), - filename:join([TargetDir, filename:dirname(BaseFile), - filename:basename(BaseFile, SourceExt) ++ TargetExt]). + filename:join([TargetDir, filename:basename(BaseFile, SourceExt) ++ TargetExt]). remove_common_path(Fname, Path) -> @@ -110,59 +129,80 @@ remove_common_path1(FilenameParts, _) -> filename:join(FilenameParts). - -compile(Source, Config, CompileFn) -> - case CompileFn(Source, Config) of - ok -> - ok; - skipped -> - skipped - end. - - compile_each([], _Config, _CompileFn) -> ok; -compile_each([Source | Rest], Config, CompileFn) -> - case compile(Source, Config, CompileFn) of +compile_each([Unit | Rest], Config, CompileFn) -> + case CompileFn(Unit, Config) of ok -> - ?CONSOLE("Compiled ~s\n", [Source]); + ?CONSOLE("Compiled ~s\n", [unit_source(Unit)]); + {ok, Warnings} -> + report(Warnings), + ?CONSOLE("Compiled ~s\n", [unit_source(Unit)]); skipped -> - ?INFO("Skipped ~s\n", [Source]) + ?INFO("Skipped ~s\n", [unit_source(Unit)]); + Error -> + maybe_report(Error), + ?CONSOLE("Compiling ~s failed:\n", + [maybe_absname(Config, unit_source(Unit))]), + ?DEBUG("Compilation failed: ~p\n", [Error]), + case rebar_config:get_xconf(Config, keep_going, false) of + false -> + ?FAIL; + true -> + ?WARN("Continuing after build error\n", []) + end end, compile_each(Rest, Config, CompileFn). +unit_source({Source, _Target}) -> + Source; +unit_source(Source) -> + Source. - -compile_queue([], []) -> +compile_queue(_Config, [], []) -> ok; -compile_queue(Pids, Targets) -> +compile_queue(Config, Pids, Targets) -> receive {next, Worker} -> case Targets of [] -> Worker ! empty, - compile_queue(Pids, Targets); + compile_queue(Config, Pids, Targets); [Source | Rest] -> Worker ! {compile, Source}, - compile_queue(Pids, Rest) + compile_queue(Config, Pids, Rest) end; - {fail, Error} -> + {fail, {_, {source, Unit}}=Error} -> + maybe_report(Error), + ?CONSOLE("Compiling ~s failed:\n", + [maybe_absname(Config, unit_source(Unit))]), ?DEBUG("Worker compilation failed: ~p\n", [Error]), - ?FAIL; + case rebar_config:get_xconf(Config, keep_going, false) of + false -> + ?FAIL; + true -> + ?WARN("Continuing after build error\n", []), + compile_queue(Config, Pids, Targets) + end; - {compiled, Source} -> - ?CONSOLE("Compiled ~s\n", [Source]), - compile_queue(Pids, Targets); - - {skipped, Source} -> - ?INFO("Skipped ~s\n", [Source]), - compile_queue(Pids, Targets); + {compiled, Unit, Warnings} -> + report(Warnings), + ?CONSOLE("Compiled ~s\n", [unit_source(Unit)]), + compile_queue(Config, Pids, Targets); + + {compiled, Unit} -> + ?CONSOLE("Compiled ~s\n", [unit_source(Unit)]), + compile_queue(Config, Pids, Targets); + + {skipped, Unit} -> + ?INFO("Skipped ~s\n", [unit_source(Unit)]), + compile_queue(Config, Pids, Targets); {'DOWN', Mref, _, Pid, normal} -> ?DEBUG("Worker exited cleanly\n", []), Pids2 = lists:delete({Pid, Mref}, Pids), - compile_queue(Pids2, Targets); + compile_queue(Config, Pids2, Targets); {'DOWN', _Mref, _, _Pid, Info} -> ?DEBUG("Worker failed: ~p\n", [Info]), @@ -173,7 +213,10 @@ QueuePid ! {next, self()}, receive {compile, Source} -> - case catch(compile(Source, Config, CompileFn)) of + case catch(CompileFn(Source, Config)) of + {ok, Ws} -> + QueuePid ! {compiled, Source, Ws}, + compile_worker(QueuePid, Config, CompileFn); ok -> QueuePid ! {compiled, Source}, compile_worker(QueuePid, Config, CompileFn); @@ -181,11 +224,66 @@ QueuePid ! {skipped, Source}, compile_worker(QueuePid, Config, CompileFn); Error -> - QueuePid ! {fail, [{error, Error}, - {source, Source}]}, - ok + QueuePid ! {fail, {{error, Error}, {source, Source}}}, + case rebar_config:get_xconf(Config, keep_going, false) of + false -> + ok; + true -> + compile_worker(QueuePid, Config, CompileFn) + end end; empty -> ok end. + +format_errors(Config, Source, Errors) -> + format_errors(Config, Source, "", Errors). + +format_warnings(Config, Source, Warnings) -> + format_warnings(Config, Source, Warnings, []). + +format_warnings(Config, Source, Warnings, Opts) -> + Prefix = case lists:member(warnings_as_errors, Opts) of + true -> ""; + false -> "Warning: " + end, + format_errors(Config, Source, Prefix, Warnings). + +maybe_report({{error, {error, _Es, _Ws}=ErrorsAndWarnings}, {source, _}}) -> + maybe_report(ErrorsAndWarnings); +maybe_report([{error, E}, {source, S}]) -> + report(["unexpected error compiling " ++ S, io_lib:fwrite("~n~p~n", [E])]); +maybe_report({error, Es, Ws}) -> + report(Es), + report(Ws); +maybe_report(_) -> + ok. + +report(Messages) -> + lists:foreach(fun(Msg) -> io:format("~s", [Msg]) end, Messages). + +format_errors(Config, _MainSource, Extra, Errors) -> + [begin + AbsSource = maybe_absname(Config, Source), + [format_error(AbsSource, Extra, Desc) || Desc <- Descs] + end + || {Source, Descs} <- Errors]. + +format_error(AbsSource, Extra, {{Line, Column}, Mod, Desc}) -> + ErrorDesc = Mod:format_error(Desc), + ?FMT("~s:~w:~w: ~s~s~n", [AbsSource, Line, Column, Extra, ErrorDesc]); +format_error(AbsSource, Extra, {Line, Mod, Desc}) -> + ErrorDesc = Mod:format_error(Desc), + ?FMT("~s:~w: ~s~s~n", [AbsSource, Line, Extra, ErrorDesc]); +format_error(AbsSource, Extra, {Mod, Desc}) -> + ErrorDesc = Mod:format_error(Desc), + ?FMT("~s: ~s~s~n", [AbsSource, Extra, ErrorDesc]). + +maybe_absname(Config, Filename) -> + case rebar_utils:processing_base_dir(Config) of + true -> + Filename; + false -> + filename:absname(Filename) + end. diff -Nru rebar-2.0.0/src/rebar_cleaner.erl rebar-2.6.0/src/rebar_cleaner.erl --- rebar-2.0.0/src/rebar_cleaner.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_cleaner.erl 2015-06-19 16:14:28.000000000 +0000 @@ -28,6 +28,9 @@ -export([clean/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -37,3 +40,17 @@ %% Get a list of files to delete from config and remove them FilesToClean = rebar_config:get(Config, clean_files, []), lists:foreach(fun (F) -> rebar_file_utils:rm_rf(F) end, FilesToClean). + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, clean) -> + ?CONSOLE( + "Delete list of files.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + {clean_files, ["file", "file2"]} + ]). diff -Nru rebar-2.0.0/src/rebar_config.erl rebar-2.6.0/src/rebar_config.erl --- rebar-2.0.0/src/rebar_config.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_config.erl 2015-06-19 16:14:28.000000000 +0000 @@ -26,37 +26,70 @@ %% ------------------------------------------------------------------- -module(rebar_config). --export([new/0, new/1, base_config/1, consult_file/1, - get/3, get_local/3, get_list/3, +-export([new/0, + new/1, + base_config/1, + consult_file/1, + 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_recursive/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"). +-ifdef(namespaced_types). +%% dict:dict() exists starting from Erlang 17. +-type rebar_dict() :: dict:dict(term(), term()). +-else. +%% dict() has been obsoleted in Erlang 17 and deprecated in 18. +-type rebar_dict() :: dict(). +-endif. + +-type key() :: atom(). + -record(config, { dir :: file:filename(), opts = [] :: list(), - envs = new_env() :: dict() }). + globals = new_globals() :: rebar_dict(), + envs = new_env() :: rebar_dict(), + %% cross-directory/-command config + skip_dirs = new_skip_dirs() :: rebar_dict(), + xconf = new_xconf() :: rebar_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). +-spec base_config(config()) -> config(). +base_config(GlobalConfig) -> + ConfName = rebar_config:get_global(GlobalConfig, config, ?DEFAULT_NAME), + new(GlobalConfig, ConfName). +-spec new() -> config(). new() -> #config{dir = rebar_utils:get_cwd()}. +-spec new(file:filename() | config()) -> config(). new(ConfigFile) when is_list(ConfigFile) -> case consult_file(ConfigFile) of {ok, Opts} -> @@ -65,70 +98,56 @@ 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). +-spec get(config(), key(), term()) -> term(). get(Config, Key, Default) -> proplists:get_value(Key, Config#config.opts, Default). +-spec get_list(config(), key(), term()) -> term(). get_list(Config, Key, Default) -> get(Config, Key, Default). +-spec get_local(config(), key(), term()) -> term(). get_local(Config, Key, Default) -> proplists:get_value(Key, local_opts(Config#config.opts, []), Default). +-spec get_all(config(), key()) -> list(term()). get_all(Config, Key) -> proplists:get_all_values(Key, Config#config.opts). +-spec set(config(), key(), term()) -> config(). 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 -> +-spec set_global(config(), key(), term()) -> config(). +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}. + +-spec get_global(config(), key(), term()) -> term(). +get_global(Config, Key, Default) -> + case dict:find(Key, Config#config.globals) of + error -> Default; {ok, Value} -> Value end. -is_verbose() -> - DefaulLevel = rebar_log:default_level(), - get_global(verbose, DefaulLevel) > DefaulLevel. - -get_jobs() -> - get_global(jobs, 3). +-spec is_recursive(config()) -> boolean(). +is_recursive(Config) -> + get_xconf(Config, recursive, false). +-spec consult_file(file:filename()) -> term(). consult_file(File) -> case filename:extension(File) of ".script" -> @@ -144,43 +163,128 @@ end end. -set_env(Config, Mod, Env) -> - OldEnvs = Config#config.envs, - NewEnvs = dict:store(Mod, Env, OldEnvs), - Config#config{envs=NewEnvs}. +-spec save_env(config(), module(), nonempty_list()) -> config(). +save_env(Config, Mod, Env) -> + NewEnvs = dict:store(Mod, Env, Config#config.envs), + Config#config{envs = NewEnvs}. +-spec get_env(config(), module()) -> term(). get_env(Config, Mod) -> dict:fetch(Mod, Config#config.envs). +-spec reset_envs(config()) -> config(). +reset_envs(Config) -> + Config#config{envs = new_env()}. + +-spec set_skip_dir(config(), file:filename()) -> config(). +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}. + +-spec is_skip_dir(config(), file:filename()) -> boolean(). +is_skip_dir(Config, Dir) -> + dict:is_key(Dir, Config#config.skip_dirs). + +-spec reset_skip_dirs(config()) -> config(). +reset_skip_dirs(Config) -> + Config#config{skip_dirs = new_skip_dirs()}. + +-spec set_xconf(config(), term(), term()) -> config(). +set_xconf(Config, Key, Value) -> + NewXconf = dict:store(Key, Value, Config#config.xconf), + Config#config{xconf=NewXconf}. + +-spec get_xconf(config(), term()) -> term(). +get_xconf(Config, Key) -> + {ok, Value} = dict:find(Key, Config#config.xconf), + Value. + +-spec get_xconf(config(), term(), term()) -> term(). +get_xconf(Config, Key, Default) -> + case dict:find(Key, Config#config.xconf) of + error -> + Default; + {ok, Value} -> + Value + end. + +-spec erase_xconf(config(), term()) -> config(). +erase_xconf(Config, Key) -> + NewXconf = dict:erase(Key, Config#config.xconf), + Config#config{xconf = NewXconf}. + +%% TODO: reconsider after config inheritance removal/redesign +-spec clean_config(config(), config()) -> config(). +clean_config(Old, New) -> + New#config{opts=Old#config.opts}. + %% =================================================================== %% Internal functions %% =================================================================== +-spec new(config(), file:filename()) -> config(). +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}. + +-spec consult_and_eval(file:filename(), file:filename()) -> {ok, term()}. consult_and_eval(File, Script) -> ?DEBUG("Evaluating config script ~p~n", [Script]), ConfigData = try_consult(File), file:script(Script, bs([{'CONFIG', ConfigData}, {'SCRIPT', Script}])). - +-spec remove_script_ext(file:filename()) -> file:filename(). remove_script_ext(F) -> "tpircs." ++ Rev = lists:reverse(F), lists:reverse(Rev). +-spec try_consult(file:filename()) -> term(). try_consult(File) -> case file:consult(File) of {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. +-type bs_vars() :: [{term(), term()}]. +-spec bs(bs_vars()) -> bs_vars(). bs(Vars) -> lists:foldl(fun({K,V}, Bs) -> erl_eval:add_binding(K, V, Bs) end, erl_eval:new_bindings(), Vars). +-spec local_opts(list(), list()) -> list(). local_opts([], Acc) -> lists:reverse(Acc); local_opts([local | _Rest], Acc) -> @@ -188,5 +292,14 @@ local_opts([Item | Rest], Acc) -> local_opts(Rest, [Item | Acc]). -new_env() -> - dict:new(). +-spec new_globals() -> rebar_dict(). +new_globals() -> dict:new(). + +-spec new_env() -> rebar_dict(). +new_env() -> dict:new(). + +-spec new_skip_dirs() -> rebar_dict(). +new_skip_dirs() -> dict:new(). + +-spec new_xconf() -> rebar_dict(). +new_xconf() -> dict:new(). diff -Nru rebar-2.0.0/src/rebar_core.erl rebar-2.6.0/src/rebar_core.erl --- rebar-2.0.0/src/rebar_core.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_core.erl 2015-06-19 16:14:28.000000000 +0000 @@ -27,151 +27,203 @@ -module(rebar_core). -export([process_commands/2, - skip_dir/1, - is_skip_dir/1, - skip_dirs/0]). + help/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) -> - case erlang:get(operations) of - 0 -> - %% none of the commands had an effect +-spec help(rebar_config:config(), [atom()]) -> ok. +help(ParentConfig, Commands) -> + %% get all core modules + {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules), + {ok, RawCoreModules} = application:get_env(rebar, modules), + AppDirModules = proplists:get_value(app_dir, RawCoreModules), + RelDirModules = proplists:get_value(rel_dir, RawCoreModules), + CoreModules = AnyDirModules ++ AppDirModules ++ RelDirModules, + + %% get plugin modules + Predirs = [], + Dir = rebar_utils:get_cwd(), + PredirsAssoc = remember_cwd_predirs(Dir, Predirs), + Config = maybe_load_local_config(Dir, ParentConfig), + {ok, PluginModules} = plugin_modules(Config, PredirsAssoc), + + AllModules = CoreModules ++ PluginModules, + + lists:foreach( + fun(Cmd) -> + ?CONSOLE("==> help ~p~n~n", [Cmd]), + CmdModules = select_modules(AllModules, Cmd, []), + Modules = select_modules(CmdModules, info, []), + lists:foreach(fun(M) -> + ?CONSOLE("=== ~p:~p ===~n", [M, Cmd]), + M:info(help, Cmd), + ?CONSOLE("~n", []) + end, Modules) + end, Commands). + +-spec process_commands([atom()], rebar_config:config()) -> ok. +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 + ?FAIL; + {_, true} -> + %% An abort was previously trapped ?FAIL; _ -> ok end; process_commands([Command | Rest], ParentConfig) -> %% 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), - process_commands(Rest, ParentConfig). + 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(), + Command, ParentConfig1, + 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, + %% TODO: reconsider after config inheritance removal/re-design + 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) -> +process_dir(Dir, Command, ParentConfig, DirSet) -> case filelib:is_dir(Dir) of false -> ?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]), - DirSet; - + {ParentConfig, DirSet}; true -> - AbsDir = filename:absname(Dir), - ShouldPrintDir = not (is_skip_dir(Dir) orelse processing_base_dir(Dir)), - - case ShouldPrintDir of - true -> - ?CONSOLE("==> Entering directory `~s'\n", [AbsDir]); - _ -> - ok - end, - + WouldCd = would_cd_into_dir(Dir, Command, ParentConfig), ok = file:set_cwd(Dir), Config = maybe_load_local_config(Dir, ParentConfig), %% Save the current code path and then update it with - %% lib_dirs. Children inherit parents code path, but we - %% also want to ensure that we restore everything to pristine + %% lib_dirs. Children inherit parents code path, but we also + %% want to ensure that we restore everything to pristine %% condition after processing this child CurrentCodePath = update_code_path(Config), - %% Get the list of processing modules and check each one against - %% CWD to see if it's a fit -- if it is, use that set of modules - %% to process this dir. + %% Get the list of processing modules and check each one + %% against CWD to see if it's a fit -- if it is, use that + %% set of modules to process this dir. {ok, AvailModuleSets} = application:get_env(rebar, modules), ModuleSet = choose_module_set(AvailModuleSets, Dir), - Res = maybe_process_dir(ModuleSet, Config, CurrentCodePath, - Dir, Command, DirSet), - - case ShouldPrintDir of - true -> - ?CONSOLE("==> Leaving directory `~s'\n", [AbsDir]); - false -> - ok - end, + skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, + ModuleSet, WouldCd) + end. - Res +would_cd_into_dir(Dir, Command, Config) -> + case would_cd_into_dir1(Dir, Command, Config) of + true -> + would_cd; + false -> + would_not_cd end. -maybe_process_dir({[], undefined}=ModuleSet, Config, CurrentCodePath, - Dir, Command, DirSet) -> - process_dir0(Dir, Command, DirSet, Config, CurrentCodePath, ModuleSet); -maybe_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath, - Dir, Command, DirSet) -> - case lists:suffix(".app.src", ModuleSetFile) - orelse lists:suffix(".app", ModuleSetFile) of +would_cd_into_dir1(Dir, Command, Config) -> + rebar_utils:processing_base_dir(Config, Dir) orelse + rebar_config:is_recursive(Config) orelse + is_recursive_command(Command, Config) orelse + is_generate_in_rel_dir(Command, Dir). + +%% Check whether the command is part of the built-in (or extended via +%% rebar.config) list of default-recursive commands. +is_recursive_command(Command, Config) -> + {ok, AppCmds} = application:get_env(rebar, recursive_cmds), + ConfCmds = rebar_config:get_local(Config, recursive_cmds, []), + RecursiveCmds = AppCmds ++ ConfCmds, + lists:member(Command, RecursiveCmds). + +%% If the directory we're about to process contains +%% reltool.config[.script] and the command to be applied is +%% 'generate', then it's safe to process. We do this to retain the +%% behavior of specifying {sub_dirs, ["rel"]} and have "rebar generate" +%% pick up rel/reltool.config[.script]. Without this workaround you'd +%% have to run "rebar -r generate" (which you don't want to do if you +%% have deps or other sub_dirs) or "cd rel && rebar generate". +is_generate_in_rel_dir(generate, Dir) -> + case rebar_rel_utils:is_rel_dir(Dir) of + {true, _} -> + true; + false -> + false + end; +is_generate_in_rel_dir(_, _) -> + false. + +skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, + {[], undefined}=ModuleSet, WouldCd) -> + process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, ModuleSet, + WouldCd); +skip_or_process_dir(Dir, Command, Config, DirSet, CurrentCodePath, + {_, File}=ModuleSet, WouldCd) -> + case lists:suffix(".app.src", File) + orelse lists:suffix(".app", File) of true -> %% .app or .app.src file, check if is_skipped_app - maybe_process_dir0(ModuleSetFile, ModuleSet, - Config, CurrentCodePath, Dir, - Command, DirSet); + skip_or_process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, + ModuleSet, WouldCd, File); false -> %% not an app dir, no need to consider apps=/skip_apps= - process_dir0(Dir, Command, DirSet, Config, - CurrentCodePath, ModuleSet) + process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, + ModuleSet, WouldCd) end. -maybe_process_dir0(AppFile, ModuleSet, Config, CurrentCodePath, - Dir, Command, DirSet) -> - case rebar_app_utils:is_skipped_app(AppFile) of - {true, SkippedApp} -> +skip_or_process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, ModuleSet, + WouldCd, AppFile) -> + case rebar_app_utils:is_skipped_app(Config, AppFile) of + {Config1, {true, _SkippedApp}} when Command == 'update-deps' -> + %% update-deps does its own app skipping. Unfortunately there's no + %% way to signal this to rebar_core, so we have to explicitly do it + %% here... Otherwise if you use app=, it'll skip the toplevel + %% directory and nothing will be updated. + process_dir1(Dir, Command, Config1, DirSet, CurrentCodePath, + ModuleSet, WouldCd); + {Config1, {true, SkippedApp}} -> ?DEBUG("Skipping app: ~p~n", [SkippedApp]), - increment_operations(), - DirSet; - false -> - process_dir0(Dir, Command, DirSet, Config, - CurrentCodePath, ModuleSet) + {increment_operations(Config1), DirSet}; + {Config1, false} -> + process_dir1(Dir, Command, Config1, DirSet, CurrentCodePath, + ModuleSet, WouldCd) end. -process_dir0(Dir, Command, DirSet, Config0, CurrentCodePath, - {DirModules, ModuleSetFile}) -> +process_dir1(Dir, Command, Config, DirSet, CurrentCodePath, + {DirModules, File}, WouldCd) -> + Config0 = rebar_config:set_xconf(Config, current_command, Command), + %% Get the list of modules for "any dir". This is a catch-all list %% of modules that are processed in addition to modules associated %% with this directory type. These any_dir modules are processed @@ -182,64 +234,41 @@ %% 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, File), - SubdirAssoc = remember_cwd_subdir(Dir, Predirs), + %% Remember associated pre-dirs (used for plugin lookup) + PredirsAssoc = remember_cwd_predirs(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, PredirsAssoc), + AllModules = Modules ++ PluginModules, - PluginPredirs = acc_modules(PluginModules, preprocess, - Config0, ModuleSetFile), + {Config2, PluginPredirs} = acc_modules(PluginModules, preprocess, Config1, + File), AllPredirs = Predirs ++ PluginPredirs, ?DEBUG("Predirs: ~p\n", [AllPredirs]), - DirSet2 = process_each(AllPredirs, Command, Config0, - ModuleSetFile, DirSet), + {Config3, DirSet2} = process_each(AllPredirs, Command, Config2, DirSet, + File), %% 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, + %% Maybe apply command to Dir + Config4 = maybe_execute(Dir, Command, Config3, Modules, PluginModules, + AllModules, File, WouldCd), %% 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), + {Config5, Postdirs} = acc_modules(AllModules, postprocess, Config4, File), ?DEBUG("Postdirs: ~p\n", [Postdirs]), - DirSet4 = process_each(Postdirs, Command, Config, - ModuleSetFile, DirSet3), + Res = process_each(Postdirs, Command, Config5, DirSet3, File), %% Make sure the CWD is reset properly; processing the dirs may have %% caused it to change @@ -249,55 +278,83 @@ %% 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. + +maybe_execute(Dir, Command, Config, Modules, PluginModules, AllModules, File, + would_cd) -> + %% Check that this directory is not on the skip list + case rebar_config:is_skip_dir(Config, 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]), + Config; + + false -> + %% Check for and get command specific environments + {Config1, Env} = setup_envs(Config, Modules), + + %% Execute any before_command plugins on this directory + Config2 = execute_pre(Command, PluginModules, Config1, File, Env), + + %% Execute the current command on this directory + Config3 = execute(Command, AllModules, Config2, File, Env), + + %% Execute any after_command plugins on this directory + execute_post(Command, PluginModules, Config3, File, Env) + end; +maybe_execute(_Dir, _Command, Config, _Modules, _PluginModules, _AllModules, + _File, would_not_cd) -> + Config. -remember_cwd_subdir(Cwd, Subdirs) -> +remember_cwd_predirs(Cwd, Predirs) -> Store = fun(Dir, Dict) -> case dict:find(Dir, Dict) of error -> - ?DEBUG("Associate sub_dir ~s with ~s~n", [Dir, Cwd]), + ?DEBUG("Associate sub_dir ~s with ~s~n", + [Dir, Cwd]), dict:store(Dir, Cwd, Dict); {ok, Existing} -> ?ABORT("Internal consistency assertion failed.~n" "sub_dir ~s already associated with ~s.~n" - "Duplicate sub_dirs or deps entries?", - [Dir, Existing]), - Dict + "Duplicate sub_dirs or deps entries?~n", + [Dir, Existing]) end end, - lists:foldl(Store, dict:new(), Subdirs). + lists:foldl(Store, dict:new(), Predirs). 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([Dir | Rest], Command, Config, ModuleSetFile, DirSet) -> +process_each([], _Command, Config, DirSet, _File) -> + %% reset cached (setup_env) envs + Config1 = rebar_config:reset_envs(Config), + {Config1, DirSet}; +process_each([Dir | Rest], Command, Config, DirSet, File) -> case sets:is_element(Dir, DirSet) of true -> ?DEBUG("Skipping ~s; already processed!\n", [Dir]), - process_each(Rest, Command, Config, ModuleSetFile, DirSet); + process_each(Rest, Command, Config, DirSet, File); false -> - DirSet2 = process_dir(Dir, Config, Command, DirSet), - process_each(Rest, Command, Config, ModuleSetFile, DirSet2) + {Config1, DirSet2} = process_dir(Dir, Command, Config, DirSet), + Config2 = rebar_config:clean_config(Config, Config1), + %% reset cached (setup_env) envs + Config3 = rebar_config:reset_envs(Config2), + process_each(Rest, Command, Config3, DirSet2, File) end. - %% %% Given a list of module sets from rebar.app and a directory, find %% the appropriate subset of modules for this directory @@ -329,38 +386,40 @@ execute_plugin_hook(Hook, Command, Modules, Config, ModuleFile, Env) -> HookFunction = list_to_atom(Hook ++ atom_to_list(Command)), - execute(HookFunction, Modules, Config, ModuleFile, Env). + execute(HookFunction, hook, Modules, Config, ModuleFile, Env). %% %% Execute a command across all applicable modules %% execute(Command, Modules, Config, ModuleFile, Env) -> + execute(Command, not_a_hook, Modules, Config, ModuleFile, Env). + +execute(Command, Type, Modules, Config, ModuleFile, Env) -> case select_modules(Modules, Command, []) of [] -> - Cmd = atom_to_list(Command), - case lists:prefix("pre_", Cmd) - orelse lists:prefix("post_", Cmd) of - true -> + case Type of + hook -> ok; - false -> + not_a_hook -> ?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} -> ?FAIL; {Module, {error, _} = Other} -> @@ -375,35 +434,44 @@ %% 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 [] -> no_change; Paths -> - OldPath = code:get_path(), LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []), ok = code:add_pathsa(LibPaths), - {old, OldPath} + %% track just the paths we added, so we can remove them without + %% removing other paths added by this dep + {added, LibPaths} end. restore_code_path(no_change) -> ok; -restore_code_path({old, Path}) -> +restore_code_path({added, Paths}) -> %% 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)]), + _ = [code:del_path(F) || F <- Paths, 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; expand_lib_dirs([Dir | Rest], Root, Acc) -> Apps = filelib:wildcard(filename:join([Dir, "*", "ebin"])), - FqApps = [filename:join([Root, A]) || A <- Apps], + FqApps = case filename:pathtype(Dir) of + absolute -> Apps; + _ -> [filename:join([Root, A]) || A <- Apps] + end, expand_lib_dirs(Rest, Root, Acc ++ FqApps). @@ -419,18 +487,21 @@ 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. -apply_hooks(Mode, Config, Command, Env) -> +apply_hooks(Mode, Config, Command, Env0) -> Hooks = rebar_config:get_local(Config, Mode, []), + Env = rebar_utils:patch_env(Config, Env0), lists:foreach(fun apply_hook/1, [{Env, Hook} || Hook <- Hooks, element(1, Hook) =:= Command orelse @@ -452,7 +523,7 @@ 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 @@ -463,18 +534,25 @@ 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. %% -plugin_modules(Config, SubdirAssoc) -> +plugin_modules(Config, PredirsAssoc) -> Modules = lists:flatten(rebar_config:get_all(Config, plugins)), - plugin_modules(Config, SubdirAssoc, ulist(Modules)). + ?DEBUG("Plugins requested while processing ~s: ~p~n", + [rebar_utils:get_cwd(), Modules]), + plugin_modules(Config, PredirsAssoc, ulist(Modules)). ulist(L) -> ulist(L, []). @@ -489,20 +567,22 @@ ulist(T, [H | Acc]) end. -plugin_modules(_Config, _SubdirAssoc, []) -> +plugin_modules(_Config, _PredirsAssoc, []) -> {ok, []}; -plugin_modules(Config, SubdirAssoc, Modules) -> +plugin_modules(Config, PredirsAssoc, Modules) -> FoundModules = [M || M <- Modules, code:which(M) =/= non_existing], - plugin_modules(Config, SubdirAssoc, FoundModules, Modules -- FoundModules). + plugin_modules(Config, PredirsAssoc, FoundModules, Modules -- FoundModules). -plugin_modules(_Config, _SubdirAssoc, FoundModules, []) -> +plugin_modules(_Config, _PredirsAssoc, FoundModules, []) -> {ok, FoundModules}; -plugin_modules(Config, SubdirAssoc, FoundModules, MissingModules) -> - {Loaded, NotLoaded} = load_plugin_modules(Config, SubdirAssoc, MissingModules), +plugin_modules(Config, PredirsAssoc, FoundModules, MissingModules) -> + {Loaded, NotLoaded} = load_plugin_modules(Config, PredirsAssoc, + MissingModules), AllViablePlugins = FoundModules ++ Loaded, case NotLoaded =/= [] of true -> - %% NB: we continue to ignore this situation, as did the original code + %% NB: we continue to ignore this situation, as did the + %% original code ?WARN("Missing plugins: ~p\n", [NotLoaded]); false -> ?DEBUG("Loaded plugins: ~p~n", [AllViablePlugins]), @@ -510,37 +590,51 @@ end, {ok, AllViablePlugins}. -load_plugin_modules(Config, SubdirAssoc, Modules) -> +load_plugin_modules(Config, PredirsAssoc, Modules) -> Cwd = rebar_utils:get_cwd(), - PluginDir = case rebar_config:get_local(Config, plugin_dir, undefined) of - undefined -> - filename:join(Cwd, "plugins"); - Dir -> - Dir - end, + PluginDirs = get_all_plugin_dirs(Config, Cwd, PredirsAssoc), + ?DEBUG("Plugin dirs for ~s:~n~p~n", [Cwd, PluginDirs]), %% Find relevant sources in base_dir and plugin_dir - Erls = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"), - RE = "^" ++ Erls ++ "\$", - BaseDir = get_plugin_base_dir(Cwd, SubdirAssoc), + RE = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"), %% If a plugin is found both in base_dir and plugin_dir, the clash %% will provoke an error and we'll abort. - Sources = rebar_utils:find_files(PluginDir, RE, false) - ++ rebar_utils:find_files(BaseDir, RE, false), + Sources = [rebar_utils:find_files(PD, RE, false) || PD <- PluginDirs], %% Compile and load plugins - Loaded = [load_plugin(Src) || Src <- Sources], + Loaded = [load_plugin(Src) || Src <- lists:append(Sources)], FilterMissing = is_missing_plugin(Loaded), NotLoaded = [V || V <- Modules, FilterMissing(V)], {Loaded, NotLoaded}. -get_plugin_base_dir(Cwd, SubdirAssoc) -> - case dict:find(Cwd, SubdirAssoc) of - {ok, BaseDir} -> - BaseDir; - error -> - Cwd - end. +get_all_plugin_dirs(Config, Cwd, PredirsAssoc) -> + [rebar_utils:get_cwd()] + ++ get_plugin_dir(Config, Cwd) + ++ get_base_plugin_dirs(Cwd, PredirsAssoc). + +get_plugin_dir(Config, Cwd) -> + case rebar_config:get_local(Config, plugin_dir, undefined) of + undefined -> + %% Plugin can be in the project's "plugins" folder + [filename:join(Cwd, "plugins")]; + Dir -> + [Dir] + end. + +%% We also want to include this case: +%% Plugin can be in "plugins" directory of the plugin base directory. +%% For example, Cwd depends on Plugin, and deps/Plugin/plugins/Plugin.erl +%% is the plugin. +get_base_plugin_dirs(Cwd, PredirsAssoc) -> + [filename:join(Dir, "plugins") || + Dir <- get_plugin_base_dirs(Cwd, PredirsAssoc)]. + +%% @doc PredirsAssoc is a dictionary of plugindir -> 'parent' pairs. +%% 'parent' in this case depends on plugin; therefore we have to give +%% all plugins that Cwd ('parent' in this case) depends on. +get_plugin_base_dirs(Cwd, PredirsAssoc) -> + [PluginDir || {PluginDir, Master} <- dict:to_list(PredirsAssoc), + Master =:= Cwd]. is_missing_plugin(Loaded) -> fun(Mod) -> not lists:member(Mod, Loaded) end. diff -Nru rebar-2.0.0/src/rebar_cover_utils.erl rebar-2.6.0/src/rebar_cover_utils.erl --- rebar-2.0.0/src/rebar_cover_utils.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_cover_utils.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,287 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com) +%% Copyright (c) 2013 Andras Horvath (andras.horvath@erlang-solutions.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_cover_utils). + +%% for internal use only +-export([init/3, + perform_cover/4, + close/1, + exit/0]). + +-include("rebar.hrl"). + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +perform_cover(Config, BeamFiles, SrcModules, TargetDir) -> + perform_cover(rebar_config:get(Config, cover_enabled, false), + Config, BeamFiles, SrcModules, TargetDir). + +perform_cover(false, _Config, _BeamFiles, _SrcModules, _TargetDir) -> + ok; +perform_cover(true, Config, BeamFiles, SrcModules, TargetDir) -> + analyze(Config, BeamFiles, SrcModules, TargetDir). + +close(not_enabled) -> + ok; +close(F) -> + ok = file:close(F). + +exit() -> + cover:stop(). + +init(false, _BeamFiles, _TargetDir) -> + {ok, not_enabled}; +init(true, BeamFiles, TargetDir) -> + %% Attempt to start the cover server, then set its group leader to + %% TargetDir/cover.log, so all cover log messages will go there instead of + %% to stdout. If the cover server is already started, we'll kill that + %% server and start a new one in order not to inherit a polluted + %% cover_server state. + {ok, CoverPid} = case whereis(cover_server) of + undefined -> + cover:start(); + _ -> + cover:stop(), + cover:start() + end, + + {ok, F} = OkOpen = file:open( + filename:join([TargetDir, "cover.log"]), + [write]), + + group_leader(F, CoverPid), + + ?INFO("Cover compiling ~s\n", [rebar_utils:get_cwd()]), + + Compiled = [{Beam, cover:compile_beam(Beam)} || Beam <- BeamFiles], + case [Module || {_, {ok, Module}} <- Compiled] of + [] -> + %% No modules compiled successfully...fail + ?ERROR("Cover failed to compile any modules; aborting.~n", []), + ?FAIL; + _ -> + %% At least one module compiled successfully + + %% It's not an error for cover compilation to fail partially, + %% but we do want to warn about them + PrintWarning = + fun(Beam, Desc) -> + ?CONSOLE("Cover compilation warning for ~p: ~p", + [Beam, Desc]) + end, + _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled], + OkOpen + end; +init(Config, BeamFiles, TargetDir) -> + init(rebar_config:get(Config, cover_enabled, false), BeamFiles, TargetDir). + +analyze(_Config, [], _SrcModules, _TargetDir) -> + ok; +analyze(Config, FilteredModules, SrcModules, TargetDir) -> + %% Generate coverage info for all the cover-compiled modules + Coverage = lists:flatten([analyze_mod(M) + || M <- FilteredModules, + cover:is_compiled(M) =/= false]), + + %% Write index of coverage info + write_index(lists:sort(Coverage), SrcModules, TargetDir), + + %% Write coverage details for each file + lists:foreach( + fun({M, _, _}) -> + {ok, _} = cover:analyze_to_file(M, + cover_file(M, TargetDir), + [html]) + end, Coverage), + + Index = filename:join([rebar_utils:get_cwd(), TargetDir, "index.html"]), + ?CONSOLE("Cover analysis: ~s\n", [Index]), + + %% Export coverage data, if configured + case rebar_config:get(Config, cover_export_enabled, false) of + true -> + export_coverdata(TargetDir); + false -> + ok + end, + + %% Print coverage report, if configured + case rebar_config:get(Config, cover_print_enabled, false) of + true -> + print_coverage(lists:sort(Coverage)); + false -> + ok + end, + + %% Generate JSON Coverage Data, if configured + case rebar_config:get(Config, cover_export_json, false) of + true -> + export_json_coverage(TargetDir, lists:sort(Coverage)); + false -> + ok + end. + +analyze_mod(Module) -> + case cover:analyze(Module, coverage, module) of + {ok, {Module, {Covered, NotCovered}}} -> + %% Modules that include the eunit header get an implicit + %% test/0 fun, which cover considers a runnable line, but + %% eunit:test(TestRepresentation) never calls. Decrement + %% NotCovered in this case. + [align_notcovered_count(Module, Covered, NotCovered, + is_eunitized(Module))]; + {error, Reason} -> + ?ERROR("Cover analyze failed for ~p: ~p ~p\n", + [Module, Reason, code:which(Module)]), + [] + end. + +is_eunitized(Mod) -> + has_eunit_test_fun(Mod) andalso + has_header(Mod, "include/eunit.hrl"). + +has_eunit_test_fun(Mod) -> + [F || {exports, Funs} <- Mod:module_info(), + {F, 0} <- Funs, F =:= test] =/= []. + +has_header(Mod, Header) -> + Mod1 = case code:which(Mod) of + cover_compiled -> + {file, File} = cover:is_compiled(Mod), + File; + non_existing -> Mod; + preloaded -> Mod; + L -> L + end, + {ok, {_, [{abstract_code, {_, AC}}]}} = + beam_lib:chunks(Mod1, [abstract_code]), + [F || {attribute, 1, file, {F, 1}} <- AC, + string:str(F, Header) =/= 0] =/= []. + +align_notcovered_count(Module, Covered, NotCovered, false) -> + {Module, Covered, NotCovered}; +align_notcovered_count(Module, Covered, NotCovered, true) -> + {Module, Covered, NotCovered - 1}. + +write_index(Coverage, SrcModules, TargetDir) -> + {ok, F} = file:open(filename:join([TargetDir, "index.html"]), [write]), + ok = file:write(F, "\n" + "" + "Coverage Summary\n" + "\n"), + IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, + {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), + write_index_section(F, "Source", SrcCoverage), + write_index_section(F, "Test", TestCoverage), + ok = file:write(F, ""), + ok = file:close(F). + +write_index_section(_F, _SectionName, []) -> + ok; +write_index_section(F, SectionName, Coverage) -> + %% Calculate total coverage + {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) -> + {CAcc + C, NAcc + N} + end, {0, 0}, Coverage), + TotalCoverage = percentage(Covered, NotCovered), + + %% Write the report + ok = file:write(F, ?FMT("

~s Summary

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

Total: ~s

\n", [TotalCoverage])), + ok = file:write(F, "\n"), + + FmtLink = + fun(Module, Cov, NotCov) -> + ?FMT("\n", + [Module, Module, percentage(Cov, NotCov)]) + end, + lists:foreach(fun({Module, Cov, NotCov}) -> + ok = file:write(F, FmtLink(Module, Cov, NotCov)) + end, Coverage), + ok = file:write(F, "
ModuleCoverage %
~s~s
\n"). + +print_coverage(Coverage) -> + {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) -> + {CAcc + C, NAcc + N} + end, {0, 0}, Coverage), + TotalCoverage = percentage(Covered, NotCovered), + + %% Determine the longest module name for right-padding + Width = lists:foldl(fun({Mod, _, _}, Acc) -> + case length(atom_to_list(Mod)) of + N when N > Acc -> + N; + _ -> + Acc + end + end, 0, Coverage) * -1, + + %% Print the output the console + ?CONSOLE("~nCode Coverage:~n", []), + lists:foreach(fun({Mod, C, N}) -> + ?CONSOLE("~*s : ~4s~n", + [Width, Mod, percentage(C, N)]) + end, Coverage), + ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]). + +export_json_coverage(TargetDir,Coverage) -> + ?CONSOLE("~nCode Coverage export to json~n", []), + lists:foreach(fun(ModuleCoverage) -> + export_json_coverage_to_file( + TargetDir, + ModuleCoverage) + end, Coverage). + +export_json_coverage_to_file(TargetDir, {Module, Covered, NotCovered}) -> + {ok, JsonFile} = file:open(json_file(TargetDir, Module), [write]), + io:format(JsonFile, + "{\"module\":~p,\"covered\":~p,\"not_covered\":~p}", + [atom_to_list(Module), Covered, NotCovered]), + ok = file:close(JsonFile). + +json_file(TargetDir, Module) -> + filename:join([TargetDir, atom_to_list(Module) ++ ".COVER.json"]). + +cover_file(Module, TargetDir) -> + filename:join([TargetDir, atom_to_list(Module) ++ ".COVER.html"]). + +export_coverdata(TargetDir) -> + ExportFile = filename:join(TargetDir, "cover.coverdata"), + case cover:export(ExportFile) of + ok -> + ?CONSOLE("Coverdata export: ~s~n", [ExportFile]); + {error, Reason} -> + ?ERROR("Coverdata export failed: ~p~n", [Reason]) + end. + +percentage(0, 0) -> + "not executed"; +percentage(Cov, NotCov) -> + integer_to_list(trunc((Cov / (Cov + NotCov)) * 100)) ++ "%". diff -Nru rebar-2.0.0/src/rebar_ct.erl rebar-2.6.0/src/rebar_ct.erl --- rebar-2.0.0/src/rebar_ct.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_ct.erl 2015-06-19 16:14:28.000000000 +0000 @@ -26,19 +26,22 @@ %% ------------------------------------------------------------------- %% %% Targets: -%% test - runs common test suites in ./test -%% int_test - runs suites in ./int_test -%% perf_test - runs suites inm ./perf_test +%% test - run common test suites in ./test +%% int_test - run suites in ./int_test +%% perf_test - run suites inm ./perf_test %% %% Global options: %% verbose=1 - show output from the common_test run as it goes -%% suites="foo,bar" - runs /foo_SUITE and /bar_SUITE -%% case="mycase" - runs individual test case foo_SUITE:mycase +%% suites="foo,bar" - run /foo_SUITE and /bar_SUITE +%% case="mycase" - run individual test case foo_SUITE:mycase %% ------------------------------------------------------------------- -module(rebar_ct). -export([ct/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -47,36 +50,86 @@ 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) -> + +info(help, ct) -> + ?CONSOLE( + "Run common_test suites.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + "Valid command line options:~n" + " suites=Suite1,Suite2,...,SuiteN~n" + " - run Suite1_SUITE, Suite2_SUITE, ..., SuiteN_SUITE~n" + " in the test folder.~n" + " groups=Group1,Group2,...,GroupN~n" + " - run test groups Group1, Group2, ..., GroupN of specified suites.~n" + " cases=Case1,Case2,...,CaseM~n" + " - run test cases Case1, Case2, ..., CaseN of specified suites.~n" + " case=\"mycase\" - run individual test case Suite1_SUITE:mycase.~n" + " This option is deprecated and remains for backward compability.~n" + " It is recommended to use 'cases' instead.~n", + [ + {ct_dir, "itest"}, + {ct_log_dir, "test/logs"}, + {ct_extra_params, "-boot start_sasl -s myapp"}, + {ct_use_short_names, true} + ]). + +run_test_if_present(TestDir, LogDir, 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; + _ -> + try + run_test(TestDir, LogDir, Config, File) + catch + throw:skip -> + ok + end + 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, - - rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]), - check_log(RawLog). - +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_log:is_verbose(Config) of + false -> + " >> " ++ RawLog ++ " 2>&1"; + true -> + " 2>&1 | tee -a " ++ RawLog + end, + + ShOpts = [{env,[{"TESTDIR", TestDir}]}, return_on_error], + case rebar_utils:sh(Cmd ++ Output, ShOpts) of + {ok,_} -> + %% in older versions of ct_run, this could have been a failure + %% that returned a non-0 code. Check for that! + check_success_log(Config, RawLog); + {error,Res} -> + %% In newer ct_run versions, this may be a sign of a good compile + %% that failed cases. In older version, it's a worse error. + check_fail_log(Config, RawLog, Cmd ++ Output, Res) + end. -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", @@ -88,31 +141,46 @@ %% calling ct with erl does not return non-zero on failure - have to check %% log results -check_log(RawLog) -> +check_success_log(Config, RawLog) -> + check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end). + +-type err_handler() :: fun((string()) -> no_return()). +-spec failure_logger(string(), {integer(), string()}) -> err_handler(). +failure_logger(Command, {Rc, Output}) -> + fun(_Msg) -> + ?ABORT("~s failed with error: ~w and output:~n~s~n", + [Command, Rc, Output]) + end. + +check_fail_log(Config, RawLog, Command, Result) -> + check_log(Config, RawLog, failure_logger(Command, Result)). + +check_log(Config,RawLog,Fun) -> {ok, Msg} = - rebar_utils:sh("grep -e 'TEST COMPLETE' -e '{error,make_failed}' " + rebar_utils:sh("grep -e \"TEST COMPLETE\" -e \"{error,make_failed}\" " ++ RawLog, [{use_stdout, false}]), MakeFailed = string:str(Msg, "{error,make_failed}") =/= 0, RunFailed = string:str(Msg, ", 0 failed") =:= 0, if MakeFailed -> - show_log(RawLog), + show_log(Config, RawLog), ?ERROR("Building tests failed\n",[]), ?FAIL; RunFailed -> - show_log(RawLog), + show_log(Config, RawLog), ?ERROR("One or more tests failed\n",[]), ?FAIL; true -> - ?CONSOLE("DONE.\n~s\n", [Msg]) + Fun(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_log:is_verbose(Config) of false -> {ok, Contents} = file:read_file(RawLog), ?CONSOLE("~s", [Contents]); @@ -120,9 +188,9 @@ 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 @@ -132,6 +200,15 @@ "" end, + %% Check for the availability of ct_run; if we can't find it, generate a + %% warning and use the old school, less reliable approach to running CT. + BaseCmd = case os:find_executable("ct_run") of + false -> + "erl -noshell -s ct_run script_start -s erlang halt"; + _ -> + "ct_run -noshell" + end, + %% Add the code path of the rebar process to the code path. This %% includes the dependencies in the code path. The directories %% that are part of the root Erlang install are filtered out to @@ -141,50 +218,63 @@ CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [EbinDir|NonLibCodeDirs]], CodePathString = string:join(CodeDirs, " "), - Cmd = case get_ct_specs(Cwd) of + Cmd = case get_ct_specs(Config, Cwd) of undefined -> - ?FMT("erl " % should we expand ERL_PATH? - " -noshell -pa ~s ~s" - " -name test@~s" - " -logdir \"~s\"" - " -env TEST_DIR \"~s\"" + ?FMT("~s" + " -pa ~s" " ~s" - " -s ct_run script_start -s erlang halt", - [CodePathString, + " ~s" + " -logdir \"~s\"" + " -env TEST_DIR \"~s\"", + [BaseCmd, + CodePathString, Include, - net_adm:localhost(), + build_name(Config), LogDir, - filename:join(Cwd, TestDir), - get_extra_params(Config)]) ++ + filename:join(Cwd, TestDir)]) ++ get_cover_config(Config, Cwd) ++ get_ct_config_file(TestDir) ++ - get_config_file(TestDir) ++ - get_suite(TestDir) ++ - get_case(); + get_suites(Config, TestDir) ++ + get_groups(Config) ++ + get_cases(Config) ++ + get_extra_params(Config) ++ + get_config_file(TestDir); SpecFlags -> - ?FMT("erl " % should we expand ERL_PATH? - " -noshell -pa ~s ~s" - " -name test@~s" - " -logdir \"~s\"" - " -env TEST_DIR \"~s\"" + ?FMT("~s" + " -pa ~s" " ~s" - " -s ct_run script_start -s erlang halt", - [CodePathString, + " ~s" + " -logdir \"~s\"" + " -env TEST_DIR \"~s\"", + [BaseCmd, + CodePathString, Include, - net_adm:localhost(), + build_name(Config), LogDir, - filename:join(Cwd, TestDir), - get_extra_params(Config)]) ++ - SpecFlags ++ get_cover_config(Config, Cwd) + filename:join(Cwd, TestDir)]) ++ + SpecFlags ++ + get_cover_config(Config, Cwd) ++ + get_extra_params(Config) end, RawLog = filename:join(LogDir, "raw.log"), {Cmd, RawLog}. +build_name(Config) -> + case rebar_config:get_local(Config, ct_use_short_names, false) of + true -> "-sname test"; + false -> " -name test@" ++ net_adm:localhost() + end. + get_extra_params(Config) -> - rebar_config:get_local(Config, ct_extra_params, ""). + case rebar_config:get_local(Config, ct_extra_params, undefined) of + undefined -> + ""; + Defined -> + " " ++ Defined + end. -get_ct_specs(Cwd) -> - case collect_glob(Cwd, ".*\.test\.spec\$") of +get_ct_specs(Config, Cwd) -> + case collect_glob(Config, Cwd, ".*\.test\.spec\$") of [] -> undefined; [Spec] -> " -spec " ++ Spec; @@ -198,31 +288,46 @@ false -> ""; true -> - case collect_glob(Cwd, ".*cover\.spec\$") of + case collect_glob(Config, Cwd, ".*cover\.spec\$") of [] -> ?DEBUG("No cover spec found: ~s~n", [Cwd]), ""; [Spec] -> - ?DEBUG("Found cover file ~w~n", [Spec]), + ?DEBUG("Found cover file ~s~n", [Spec]), " -cover " ++ Spec; Specs -> ?ABORT("Multiple cover specs found: ~p~n", [Specs]) end end. -collect_glob(Cwd, Glob) -> - filelib:fold_files(Cwd, Glob, true, fun collect_files/2, []). - -collect_files(F, Acc) -> - %% Ignore any specs under the deps/ directory. Do this pulling - %% the dirname off the the F and then splitting it into a list. - Parts = filename:split(filename:dirname(F)), - case lists:member("deps", Parts) of - true -> - Acc; % There is a directory named "deps" in path - false -> - [F | Acc] % No "deps" directory in path - end. +collect_glob(Config, Cwd, Glob) -> + {true, Deps} = rebar_deps:get_deps_dir(Config), + DepsDir = filename:basename(Deps), + CwdParts = filename:split(Cwd), + filelib:fold_files( + Cwd, + Glob, + true, + fun(F, Acc) -> + %% Ignore any specs under the deps/ directory. Do this pulling + %% the dirname off the F and then splitting it into a list. + Parts = filename:split(filename:dirname(F)), + Parts2 = remove_common_prefix(Parts, CwdParts), + case lists:member(DepsDir, Parts2) of + true -> + %% There is a directory named "deps" in path + Acc; + false -> + %% No "deps" directory in path + [F | Acc] + end + end, + []). + +remove_common_prefix([H1|T1], [H1|T2]) -> + remove_common_prefix(T1, T2); +remove_common_prefix(L1, _) -> + L1. get_ct_config_file(TestDir) -> Config = filename:join(TestDir, "test.config"), @@ -239,33 +344,80 @@ false -> " "; true -> - " -config " ++ Config + " -erl_args -config " ++ Config end. -get_suite(TestDir) -> - case rebar_utils:get_deprecated_global(suite, suites, "soon") of +get_suites(Config, TestDir) -> + case get_suites(Config) of undefined -> " -dir " ++ TestDir; Suites -> - Suites1 = string:tokens(Suites, ","), - Suites2 = [find_suite_path(Suite, TestDir) || Suite <- Suites1], - string:join([" -suite"] ++ Suites2, " ") + Suites1 = [find_suite_path(Suite, TestDir) || Suite <- Suites], + string:join([" -suite"] ++ Suites1, " ") + end. + +get_suites(Config) -> + case rebar_config:get_global(Config, suites, undefined) of + undefined -> + %% The option 'suite' is deprecated and remains + %% for backward compatibility. + %% It is recommended to use 'suites' instead. + case get_deprecated_global(Config, suite, suites) of + undefined -> + undefined; + Suite -> + [Suite] + end; + Suites -> + string:tokens(Suites, ",") end. find_suite_path(Suite, TestDir) -> Path = filename:join(TestDir, Suite ++ "_SUITE.erl"), case filelib:is_regular(Path) of false -> - ?ERROR("Suite ~s not found\n", [Suite]), - ?FAIL; + ?WARN("Suite ~s not found\n", [Suite]), + %% Note - this throw is caught in run_test_if_present/3; + %% this solution was easier than refactoring the entire module. + throw(skip); true -> Path end. -get_case() -> - case rebar_config:get_global('case', undefined) of +get_groups(Config) -> + case rebar_config:get_global(Config, groups, undefined) of undefined -> - ""; - Case -> - " -case " ++ Case + %% The option 'group' was added only for consistency + %% because there are options 'suite' and 'case'. + case get_deprecated_global(Config, group, groups) of + undefined -> + ""; + Group -> + " -group " ++ Group + end; + Groups -> + Groups1 = string:tokens(Groups, ","), + string:join([" -group"] ++ Groups1, " ") end. + +get_cases(Config) -> + case rebar_config:get_global(Config, cases, undefined) of + undefined -> + %% The option 'case' is deprecated and remains + %% for backward compatibility. + %% It is recommended to use 'cases' instead. + case get_deprecated_global(Config, 'case', cases) of + undefined -> + ""; + Case -> + " -case " ++ Case + end; + Cases -> + Cases1 = string:tokens(Cases, ","), + string:join([" -case"] ++ Cases1, " ") + end. + +get_deprecated_global(Config, OldOpt, NewOpt) -> + rebar_utils:get_deprecated_global( + Config, OldOpt, NewOpt, undefined, "in the future"). + diff -Nru rebar-2.0.0/src/rebar_deps.erl rebar-2.6.0/src/rebar_deps.erl --- rebar-2.0.0/src/rebar_deps.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_deps.erl 2015-06-19 16:14:28.000000000 +0000 @@ -38,11 +38,15 @@ 'delete-deps'/2, 'list-deps'/2]). +%% for internal use only +-export([info/2]). +-export([get_deps_dir/1]). -record(dep, { dir, app, vsn_regex, - source }). + source, + is_raw }). %% is_raw = true means non-Erlang/OTP dependency %% =================================================================== %% Public API @@ -52,50 +56,87 @@ %% 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); + %% 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], + + case rebar_config:get_xconf(Config, current_command, undefined) of + 'update-deps' -> + %% Skip ALL of the dep folders, we do this because we don't want + %% any other calls to preprocess() for update-deps beyond the + %% toplevel directory. They aren't actually harmful, but they slow + %% things down unnecessarily. + NewConfig = lists:foldl( + fun(D, Acc) -> + rebar_config:set_skip_dir(Acc, D#dep.dir) + end, + Config3, + collect_deps(rebar_utils:get_cwd(), Config3)), + %% Return the empty list, as we don't want anything processed before + %% us. + {ok, NewConfig, []}; _ -> - ok - end, - - %% Return all the available dep directories for process - {ok, [D#dep.dir || D <- 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. + %% + %% Also, if skip_deps=comma,separated,app,list, then only the given + %% dependencies are skipped. + 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); + Apps when is_list(Apps) -> + SkipApps = [list_to_atom(App) || + App <- string:tokens(Apps, ",")], + lists:foldl( + fun(#dep{dir = Dir, app = App}, C) -> + case lists:member(App, SkipApps) of + true -> rebar_config:set_skip_dir(C, Dir); + false -> C + end + end, Config3, AvailableDeps); + _ -> + Config3 + end, + %% Return all the available dep directories for process + {ok, NewConfig, dep_dirs(NonRawAvailableDeps)} + end. -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,14 +152,15 @@ 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]) @@ -126,70 +168,154 @@ ?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], + Config1 = rebar_config:set_xconf(Config, depowner, dict:new()), + {Config2, UpdatedDeps} = update_deps_int(Config1, []), + DepOwners = rebar_config:get_xconf(Config2, depowner, dict:new()), + + %% check for conflicting deps + _ = [?ERROR("Conflicting dependencies for ~p: ~p~n", + [K, [{"From: " ++ string:join(dict:fetch(D, DepOwners), ", "), + {D#dep.vsn_regex, D#dep.source}} || D <- V]]) + || {K, V} <- dict:to_list( + lists:foldl( + fun(Dep, Acc) -> + dict:append(Dep#dep.app, Dep, Acc) + end, dict:new(), UpdatedDeps)), + length(V) > 1], + %% 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(Config, 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 %% =================================================================== +info(help, compile) -> + info_help("Display to be fetched dependencies"); +info(help, 'check-deps') -> + info_help("Display to be fetched dependencies"); +info(help, 'get-deps') -> + info_help("Fetch dependencies"); +info(help, 'update-deps') -> + info_help("Update fetched dependencies"); +info(help, 'delete-deps') -> + info_help("Delete fetched dependencies"); +info(help, 'list-deps') -> + info_help("List dependencies"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + "Valid command line options:~n" + " deps_dir=\"deps\" (override default or rebar.config deps_dir)~n", + [ + Description, + {deps_dir, "deps"}, + {deps, + [app_name, + {rebar, "1.0.*"}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git"}}, + {rebar, ".*", + {git, "git://github.com/rebar/rebar.git", "Rev"}}, + {rebar, "1.0.*", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}}, + {rebar, "1.0.0", + {git, "git://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, + {rebar, "", + {git, "git://github.com/rebar/rebar.git", {branch, "master"}}, + [raw]}, + {app_name, ".*", {hg, "https://www.example.org/url"}}, + {app_name, ".*", {rsync, "Url"}}, + {app_name, ".*", {svn, "https://www.example.org/url"}}, + {app_name, ".*", {svn, "svn://svn.example.org/url"}}, + {app_name, ".*", {bzr, "https://www.example.org/url", "Rev"}}, + {app_name, ".*", {fossil, "https://www.example.org/url"}}, + {app_name, ".*", {fossil, "https://www.example.org/url", "Vsn"}}, + {app_name, ".*", {p4, "//depot/subdir/app_dir"}}]} + ]). + %% Added because of trans deps, %% need all deps in same dir and should be the one set by the root rebar.config +%% In case one is given globally, it has higher priority %% 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, []) -> + LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"), + GlobalDepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir), + DepsDir = case os:getenv("REBAR_DEPS_DIR") of + false -> + GlobalDepsDir; + Dir -> + Dir + end, + 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_utils:base_dir(Config), + DepsDir0 = get_shared_deps_dir(Config, "deps"), + DepsDir = filename:dirname(filename:join([BaseDir, DepsDir0, "dummy"])), + {true, filename:join([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 +326,84 @@ 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 +426,88 @@ 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}} + case filelib:is_dir(Path) of + true -> + %% Path is a directory, but it's not an app dir. + ?WARN("Directory expected to be an app dir, but no " + "app file found ~n" + "in ebin/ or src/:~n~s~n", + [Path]); + false -> + %% Path is not a directory, so it cannot be an app dir. + %% TODO: maybe we can avoid checking non-existing dirs + ?DEBUG("Directory expected to be an app dir, " + "but it doesn't exist (yet?):~n~s~n", [Path]) + end, + {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 /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,11 +516,45 @@ 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. +-record(p4_settings, { + client=undefined, + transport="tcp4:perforce:1666", + username, + password + }). +init_p4_settings(Basename) -> + #p4_settings{client = + case inet:gethostname() of + {ok,HostName} -> + HostName ++ "-" + ++ os:getenv("USER") ++ "-" + ++ Basename + ++ "-Rebar-automated-download" + end}. + +download_source(AppDir, {p4, Url}) -> + download_source(AppDir, {p4, Url, "#head"}); +download_source(AppDir, {p4, Url, Rev}) -> + download_source(AppDir, {p4, Url, Rev, init_p4_settings(filename:basename(AppDir))}); +download_source(AppDir, {p4, Url, _Rev, Settings}) -> + ok = filelib:ensure_dir(AppDir), + rebar_utils:sh_send("p4 client -i", + ?FMT("Client: ~s~n" + ++"Description: generated by Rebar~n" + ++"Root: ~s~n" + ++"View:~n" + ++" ~s/... //~s/...~n", + [Settings#p4_settings.client, + AppDir, + Url, + Settings#p4_settings.client]), + []), + rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []); download_source(AppDir, {hg, Url, Rev}) -> ok = filelib:ensure_dir(AppDir), rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), @@ -388,19 +591,29 @@ [{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]), []). + 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, 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(Dep) -> +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 +621,119 @@ 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, Args) when element(1, Args) =:= p4 -> + download_source(AppDir, Args); +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}}) -> + rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts), + rebar_utils:sh( + ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts); +update_source1(AppDir, {git, _Url, {tag, Tag}}) -> ShOpts = [{cd, AppDir}], - rebar_utils:sh("git fetch --tags origin", ShOpts), + rebar_utils:sh("git fetch 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, Version}) -> + ok = file:set_cwd(AppDir), + rebar_utils:sh("fossil pull", [{cd, AppDir}]), + rebar_utils:sh(?FMT("fossil update ~s", [Version]), []). + +%% Recursively update deps, this is not done via rebar's usual dep traversal as +%% that is the wrong order (tips are updated before branches). Instead we do a +%% traverse the deps at each level completely before traversing *their* deps. +%% This allows updates to actually propogate down the tree, rather than fail to +%% flow up the tree, which was the previous behaviour. +update_deps_int(Config0, UDD) -> + %% Determine what deps are required + ConfDir = filename:basename(rebar_utils:get_cwd()), + RawDeps = rebar_config:get_local(Config0, deps, []), + {Config1, Deps} = find_deps(Config0, read, RawDeps), + + %% Update each dep + UpdatedDeps = [update_source(Config1, D) + || D <- Deps, D#dep.source =/= undefined, + not lists:member(D, UDD), + not should_skip_update_dep(Config1, D) + ], + + lists:foldl(fun(Dep, {Config, Updated}) -> + {true, AppDir} = get_deps_dir(Config, Dep#dep.app), + Config2 = case has_vcs_dir(element(1, Dep#dep.source), + AppDir) of + false -> + %% If the dep did not exist (maybe it + %% was added), clone it. + %% We'll traverse ITS deps below and + %% clone them if needed. + {C1, _D1} = use_source(Config, Dep), + C1; + true -> + Config + end, + ok = file:set_cwd(AppDir), + Config3 = rebar_config:new(Config2), + %% track where a dep comes from... + DepOwner = dict:append( + Dep, ConfDir, + rebar_config:get_xconf(Config3, depowner, + dict:new())), + Config4 = rebar_config:set_xconf(Config3, depowner, + DepOwner), + + {Config5, Res} = update_deps_int(Config4, Updated), + {Config5, lists:umerge(lists:sort(Res), + lists:sort(Updated))} + end, {Config1, lists:umerge(lists:sort(UpdatedDeps), + lists:sort(UDD))}, UpdatedDeps). + +should_skip_update_dep(Config, Dep) -> + {true, AppDir} = get_deps_dir(Config, Dep#dep.app), + case rebar_app_utils:is_app_dir(AppDir) of + false -> + false; + {true, AppFile} -> + case rebar_app_utils:is_skipped_app(Config, AppFile) of + {_Config, {true, _SkippedApp}} -> + true; + _ -> + false + end + end. + +%% Recursively walk the deps and build a list of them. +collect_deps(Dir, C) -> + case file:set_cwd(Dir) of + ok -> + Config = rebar_config:new(C), + RawDeps = rebar_config:get_local(Config, deps, []), + {Config1, Deps} = find_deps(Config, read, RawDeps), + + lists:flatten(Deps ++ [begin + {true, AppDir} = get_deps_dir( + Config1, Dep#dep.app), + collect_deps(AppDir, C) + end || Dep <- Deps]); + _ -> + [] + end. %% =================================================================== @@ -445,7 +745,8 @@ 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; Name == p4 -> case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of true -> true; @@ -466,12 +767,17 @@ 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(p4) -> {2013, 1}; +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(p4) -> + vcs_client_vsn(rebar_utils:find_executable("p4"), " -V", + "Rev\\. .*/(\\d+)\\.(\\d)/"); vcs_client_vsn(hg) -> vcs_client_vsn(rebar_utils:find_executable("hg"), " --version", "version (\\d+).(\\d+)"); @@ -486,8 +792,13 @@ "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(p4, _) -> + true; has_vcs_dir(git, Dir) -> filelib:is_dir(filename:join(Dir, ".git")); has_vcs_dir(hg, Dir) -> @@ -505,6 +816,8 @@ print_source(#dep{app=App, source=Source}) -> ?CONSOLE("~s~n", [format_source(App, Source)]). +format_source(App, {p4, Url}) -> + format_source(App, {p4, Url, "#head"}); format_source(App, {git, Url}) -> ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]); format_source(App, {git, Url, ""}) -> @@ -515,5 +828,7 @@ ?FMT("~p TAG ~s ~s", [App, Tag, Url]); format_source(App, {_, Url, Rev}) -> ?FMT("~p REV ~s ~s", [App, Rev, Url]); +format_source(App, {SrcType, Url}) -> + ?FMT("~p ~p ~s", [App, SrcType, Url]); format_source(App, undefined) -> ?FMT("~p", [App]). diff -Nru rebar-2.0.0/src/rebar_dia_compiler.erl rebar-2.6.0/src/rebar_dia_compiler.erl --- rebar-2.0.0/src/rebar_dia_compiler.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_dia_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,138 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_dia_compiler). + +-export([compile/2, + clean/2]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec compile(rebar_config:config(), file:filename()) -> 'ok'. +compile(Config, _AppFile) -> + DiaOpts = rebar_config:get(Config, dia_opts, []), + IncludeEbin = proplists:get_value(include, DiaOpts, []), + DiaFiles = filelib:wildcard("dia/*.dia"), + code:add_pathsz(["ebin" | IncludeEbin]), + FileSequence = case rebar_config:get(Config, dia_first_files, []) of + [] -> + DiaFiles; + CompileFirst -> + CompileFirst ++ + [F || F <- DiaFiles, not lists:member(F, CompileFirst)] + end, + rebar_base_compiler:run(Config, FileSequence, + "dia", ".dia", "src", ".erl", + fun compile_dia/3). + +-spec clean(rebar_config:config(), file:filename()) -> 'ok'. +clean(Config, _AppFile) -> + DiaOpts = rebar_config:get(Config, dia_opts, []), + IncludeEbin = proplists:get_value(include, DiaOpts, []), + code:add_pathsz(["ebin" | IncludeEbin]), + GeneratedFiles = dia_generated_files("dia", "src", "include"), + ok = rebar_file_utils:delete_each(GeneratedFiles), + ok. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, compile) -> + info_help("Build Diameter (*.dia) sources"); +info(help, clean) -> + info_help("Delete generated Diameter files"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " {dia_opts, []} (options from diameter_make:codec/2 supported with~n" + " exception of inherits)~n" + " {dia_first_files, []} (files in sequence to compile first)~n", + [Description]). + +-spec compile_dia(file:filename(), file:filename(), + rebar_config:config()) -> ok. +compile_dia(Source, Target, Config) -> + ok = filelib:ensure_dir(Target), + ok = filelib:ensure_dir(filename:join("include", "dummy.hrl")), + Opts = [{outdir, "src"}] ++ rebar_config:get(Config, dia_opts, []), + case diameter_dict_util:parse({path, Source}, []) of + {ok, Spec} -> + FileName = dia_filename(Source, Spec), + _ = diameter_codegen:from_dict(FileName, Spec, Opts, erl), + _ = diameter_codegen:from_dict(FileName, Spec, Opts, hrl), + HrlFile = filename:join("src", FileName ++ ".hrl"), + ErlFile = filename:join("src", FileName ++ ".erl"), + ErlCOpts = [{outdir, "ebin"}] ++ + rebar_config:get(Config, erl_opts, []), + _ = compile:file(ErlFile, ErlCOpts), + case filelib:is_regular(HrlFile) of + true -> + ok = rebar_file_utils:mv(HrlFile, "include"); + false -> + ok + end; + {error, Reason} -> + ?ABORT( + "Compiling ~s failed: ~s~n", + [Source, diameter_dict_util:format_error(Reason)] + ) + end. + +dia_generated_files(DiaDir, SrcDir, IncDir) -> + F = fun(File, Acc) -> + case catch diameter_dict_util:parse({path, File}, []) of + {ok, Spec} -> + FileName = dia_filename(File, Spec), + [ + filename:join([IncDir, FileName ++ ".hrl"]) | + filelib:wildcard( + filename:join([SrcDir, FileName ++ ".*"]) + ) + ] ++ Acc; + _ -> + Acc + end + end, + lists:foldl(F, [], filelib:wildcard(filename:join([DiaDir, "*.dia"]))). + +dia_filename(File, Spec) -> + case proplists:get_value(name, Spec) of + undefined -> + filename:rootname(filename:basename(File)); + Name -> + Name + end. diff -Nru rebar-2.0.0/src/rebar_dialyzer.erl rebar-2.6.0/src/rebar_dialyzer.erl --- rebar-2.0.0/src/rebar_dialyzer.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_dialyzer.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,246 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014-2015 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_dialyzer). + +-export([ + dialyze/2, + 'build-plt'/2, + 'check-plt'/2, + 'delete-plt'/2 + ]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +dialyze(Config, AppFile) -> + Opts = opts(Config), + {NewConfig, Plt} = plt(Config, AppFile, Opts), + ok = check_plt_existence(Plt), + + Args = [ + {analysis_type, succ_typings}, + %% http://erlang.org/pipermail/erlang-bugs/2015-February/004781.html + %% TODO: remove once the minimum required Erlang/OTP release + %% includes a Dialyzer version without the bug, or alternatively + %% add a config option to always check the PLT, as this may be + %% needed by some users. + {check_plt, false}, + {init_plt, Plt}, + {files_rec, ["ebin"]}, + {warnings, warnings(Opts)} + ], + ?DEBUG("dialyze opts:~n~p~n", [Args]), + case run(Args) of + [] -> + {ok, NewConfig}; + Ws -> + print_warnings(Ws, fullpath), + ?FAIL + end. + +'build-plt'(Config, AppFile) -> + Opts = opts(Config), + {Config1, AppDirs} = app_dirs(Config, AppFile, Opts), + {NewConfig, Plt} = plt(Config1, AppFile, Opts), + + Args = [ + {analysis_type, plt_build}, + {output_plt, Plt}, + {files_rec, AppDirs} + ], + ?DEBUG("build-plt opts:~n~p~n", [Args]), + case run(Args) of + [] -> + {ok, NewConfig}; + Ws -> + %% As plt_build may raise warnings but still successfully + %% create the PLT, we cannot interpret this as failure, + %% and therefore all we can do is report warnings. + print_warnings(Ws, basename) + end. + +'check-plt'(Config, AppFile) -> + Opts = opts(Config), + {NewConfig, Plt} = plt(Config, AppFile, Opts), + ok = check_plt_existence(Plt), + + Args = [ + {analysis_type, plt_check}, + %% http://erlang.org/pipermail/erlang-bugs/2015-February/004781.html + %% Without this, the PLT will be checked twice. + %% TODO: remove once the minimum required Erlang/OTP release + %% includes a Dialyzer version without the bug. + {check_plt, false}, + {init_plt, Plt} + ], + ?DEBUG("build-plt opts:~n~p~n", [Args]), + case run(Args) of + [] -> + {ok, NewConfig}; + Ws -> + print_warnings(Ws, basename), + ?FAIL + end. + +'delete-plt'(Config, AppFile) -> + Opts = opts(Config), + {NewConfig, Plt} = plt(Config, AppFile, Opts), + ?DEBUG("Delete PLT '~s'~n", [Plt]), + ok = rebar_file_utils:delete_each([Plt]), + {ok, NewConfig}. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, dialyze) -> + info_help("Analyze the code for discrepancies"); +info(help, 'build-plt') -> + info_help("Build project-specific PLT"); +info(help, 'check-plt') -> + info_help("Check the PLT for consistency and rebuild it if it" + " is not up-to-date"); +info(help, 'delete-plt') -> + info_help("Delete project-specific PLT"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + Description, + {dialyzer, + [ + {plt_location, local}, + {plt_location, "custom_dir"}, + {plt_extra_apps, [app1, app2]}, + {warnings, [unmatched_returns, error_handling]} + ]} + ]). + +opts(Config) -> + rebar_config:get_local(Config, dialyzer, []). + +plt(Config, AppFile, Opts) -> + PltDir = plt_dir(Config, Opts), + {NewConfig, RawAppName} = rebar_app_utils:app_name(Config, AppFile), + AppName = atom_to_list(RawAppName), + OtpRel = rebar_utils:otp_release(), + Plt = filename:join([PltDir, AppName ++ "_" ++ OtpRel ++ "_plt"]), + ok = filelib:ensure_dir(Plt), + {NewConfig, Plt}. + +plt_dir(Config, Opts) -> + Location = proplists:get_value(plt_location, Opts, local), + plt_dir1(Config, Location). + +plt_dir1(_Config, Location) when is_list(Location) -> + case filelib:is_dir(Location) of + false -> + ?ABORT("PLT directory does not exist: ~s~n", [Location]); + true -> + Location + end; +plt_dir1(Config, local) -> + BaseDir = rebar_utils:base_dir(Config), + filename:join([BaseDir, ".rebar"]). + +check_plt_existence(Plt) -> + case filelib:is_regular(Plt) of + true -> + ok; + false -> + ?ABORT("PLT '~s' does not exist.~n" + "Please run 'rebar build-plt' first.~n", [Plt]) + end. + +%% dialyzer:run/1 wrapper to gracefully fail in case of Dialyzer errors +run(Opts) -> + try dialyzer:run(Opts) of + Ws -> Ws + catch + throw:{dialyzer_error, Reason} -> + ?ABORT("Dialyzer error:~n~s~n", [Reason]) + end. + +warnings(Opts) -> + proplists:get_value(warnings, Opts, []). + +print_warnings(Ws, Option) -> + lists:foreach( + fun(W) -> + ?CONSOLE("~s~n", [format_warning(W, Option)]) + end, + Ws). + +format_warning(W, Option) -> + case dialyzer:format_warning(W, Option) of + ":0: " ++ Unknown -> + strip_newline(Unknown); + Warning -> + strip_newline(Warning) + end. + +%% Warning may or may not have trailing \n. +strip_newline(Warning) -> + string:strip(Warning, right, $\n). + +app_dirs(Config, AppFile, Opts) -> + {NewConfig, AppFileApps} = app_file_apps(Config, AppFile), + ?DEBUG("app file apps:~n~p~n", [AppFileApps]), + Deps = deps_apps(Config), + ?DEBUG("deps apps:~n~p~n", [Deps]), + ExtraApps = proplists:get_value(plt_extra_apps, Opts, []), + ?DEBUG("extra apps:~n~p~n", [ExtraApps]), + %% erts is assumed, and has to be present unconditionally. + Erts = [erts], + Apps = ordsets:from_list(Erts ++ AppFileApps ++ Deps ++ ExtraApps), + AppDirs = [app_lib_dir(App) || App <- ordsets:to_list(Apps)], + ?DEBUG("app dirs:~n~p~n", [AppDirs]), + {NewConfig, AppDirs}. + +app_file_apps(Config, AppFile) -> + rebar_app_utils:app_applications(Config, AppFile). + +deps_apps(Config) -> + [element(1, Dep) || Dep <- rebar_config:get_local(Config, deps, [])]. + +app_lib_dir(App) -> + case code:lib_dir(App, ebin) of + {error, _}=Err -> + ?ABORT("Failed to get ebin dir for app: ~p~n~p~n", [App, Err]); + Dir -> + Dir + end. diff -Nru rebar-2.0.0/src/rebar_edoc.erl rebar-2.6.0/src/rebar_edoc.erl --- rebar-2.0.0/src/rebar_edoc.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_edoc.erl 2015-06-19 16:14:28.000000000 +0000 @@ -39,35 +39,92 @@ -export([doc/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== %% 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(), - {ok, AppName, _AppData} = rebar_app_utils:load_app_file(File), + + %% Get the edoc_opts and app file info EDocOpts = rebar_config:get(Config, edoc_opts, []), - ok = edoc:application(AppName, ".", EDocOpts), + {ok, Config1, AppName, _AppData} = + rebar_app_utils:load_app_file(Config, File), + + case needs_regen(EDocOpts) of + true -> + ?INFO("Regenerating edocs for ~p\n", [AppName]), + ok = edoc:application(AppName, ".", EDocOpts); + false -> + ?INFO("Skipping regeneration of edocs for ~p\n", [AppName]), + ok + end, + %% Restore code path - true = code:set_path(CodePath), - ok. + true = rebar_utils:cleanup_code_path(CodePath), + {ok, Config1}. %% =================================================================== %% Internal functions %% =================================================================== +info(help, doc) -> + ?CONSOLE( + "Generate Erlang program documentation.~n" + "~n" + "Valid rebar.config options:~n" + " {edoc_opts, []} (see edoc:application/3 documentation)~n", + []). + setup_code_path() -> %% Setup code path prior to calling edoc so that edown, asciiedoc, %% and the like can work properly when generating their own %% documentation. CodePath = code:get_path(), - true = code:add_patha(ebin_dir()), + _ = 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, + + try + lists:foldl(fun({file, F}, _) -> + ThrowIfNewer(F, false); + (P, _) -> + filelib:fold_files(P, ".*.erl", true, + ThrowIfNewer, false) + end, undefined, Paths) + catch + throw:{newer_file_exists, {Filename, FMod}} -> + ?DEBUG("~p is more recent than ~p: " + "~120p > ~120p\n", + [Filename, OldFile, FMod, OldModTime]), + true + end. + +%% Needs regen if any dependent file is changed since the last +%% edoc run. Dependent files are the erlang source files, +%% and the overview file, if it exists. +-spec needs_regen(proplists:proplist()) -> boolean(). +needs_regen(EDocOpts) -> + DocDir = proplists:get_value(dir, EDocOpts, "doc"), + EDocInfoName = filename:join(DocDir, "edoc-info"), + OverviewFile = proplists:get_value(overview, EDocOpts, "overview.edoc"), + EDocOverviewName = filename:join(DocDir, OverviewFile), + SrcPaths = proplists:get_value(source_path, EDocOpts, ["src"]), + + newer_file_exists([{file, EDocOverviewName} | SrcPaths], EDocInfoName). diff -Nru rebar-2.0.0/src/rebar.erl rebar-2.6.0/src/rebar.erl --- rebar-2.0.0/src/rebar.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar.erl 2015-06-19 16:14:28.000000000 +0000 @@ -27,9 +27,11 @@ -module(rebar). -export([main/1, + run/2, help/0, parse_args/1, - version/0]). + version/0, + get_jobs/1]). -include("rebar.hrl"). @@ -45,79 +47,74 @@ -define(OTP_INFO, "undefined"). -endif. +-define(DEFAULT_JOBS, 3). + %% ==================================================================== %% Public API %% ==================================================================== +%% escript Entry point main(Args) -> case catch(run(Args)) of ok -> ok; - {error, failed} -> - halt(1); + rebar_abort -> + rebar_utils:delayed_halt(1); Error -> %% Nothing should percolate up from rebar_core; %% Dump this error to console io:format("Uncaught error in rebar_core: ~p\n", [Error]), - halt(1) + rebar_utils:delayed_halt(1) end. +%% Erlang-API entry point +run(BaseConfig, Commands) -> + _ = application:load(rebar), + run_aux(BaseConfig, Commands). + %% ==================================================================== %% Internal functions %% ==================================================================== +run(["help"|RawCmds]) when RawCmds =/= [] -> + ok = load_rebar_app(), + Cmds = unabbreviate_command_names(RawCmds), + Args = parse_args(Cmds), + BaseConfig = init_config(Args), + {BaseConfig1, _} = save_options(BaseConfig, Args), + BaseConfig2 = init_config1(BaseConfig1), + rebar_core:help(BaseConfig2, [list_to_atom(C) || C <- Cmds]); +run(["help"]) -> + help(); +run(["info"|_]) -> + %% Catch calls to 'rebar info' to avoid treating plugins' info/2 functions + %% as commands. + ?CONSOLE("Command 'info' not understood or not applicable~n", []); +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]) - after - fprof:profile(), - fprof:analyse([{dest, "fprof.analysis"}]) - end; - _ -> - run_aux(Args) + ?CONSOLE("Profiling!\n", []), + profile(BaseConfig1, Cmds); + 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(), - - %% Initialize logging system - rebar_log:init(), - - %% Initialize vsn cache - _VsnCacheTab = ets:new(rebar_vsn_cache,[named_table, public]), - - %% 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)]), - - %% 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), +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 @@ -128,78 +125,242 @@ false -> rebar_config:new() end, - BaseConfig = rebar_config:base_config(GlobalConfig), + + %% 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 + 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 + rebar_config:set_xconf(BaseConfig1, vsn_cache, dict:new()). + +init_config1(BaseConfig) -> + %% Determine the location of the rebar executable; important for pulling + %% resources out of the escript + 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 + AbsCwd = filename:absname(rebar_utils:get_cwd()), + rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd). + +profile(BaseConfig1, Commands) -> + ?CONSOLE("Please take note that profiler=[fprof|eflame] is preliminary" + " and will be~nreplaced with a different command line flag" + " in the next release.~n", []), + Profiler = rebar_config:get_global(BaseConfig1, profiler, "fprof"), + profile(BaseConfig1, Commands, list_to_atom(Profiler)). + +profile(Config, Commands, fprof) -> + try + fprof:apply(fun run_aux/2, [Config, Commands]) + after + ok = fprof:profile(), + ok = fprof:analyse([{dest, "fprof.analysis"}]), + case rebar_utils:find_executable("erlgrind") of + false -> + ?CONSOLE( + "See fprof.analysis (generated from fprof.trace)~n", []), + ok; + ErlGrind -> + Cmd = ?FMT("~s fprof.analysis fprof.cgrind", [ErlGrind]), + {ok, []} = rebar_utils:sh(Cmd, [{use_stdout, false}, + abort_on_error]), + ?CONSOLE("See fprof.analysis (generated from fprof.trace)" + " and fprof.cgrind~n", []), + ok + end + end; +profile(Config, Commands, eflame) -> + case code:lib_dir(eflame) of + {error, bad_name} -> + ?ABORT("eflame not found in code path~n", []), + ok; + EflameDir -> + Trace = "eflame.trace", + try + eflame:apply(normal_with_children, Trace, + rebar, run, [Config, Commands]) + after + %% generate flame graph + Script = filename:join(EflameDir, "stack_to_flame.sh"), + Svg = "eflame.svg", + %% stack_to_flame.sh < eflame.trace > eflame.png + Cmd = ?FMT("~s < ~s > ~s", [Script, Trace, Svg]), + {ok, []} = rebar_utils:sh(Cmd, [{use_stdout, false}, + abort_on_error]), + ?CONSOLE("See eflame.svg (generated from eflame.trace)~n", []), + ok + end + end; +profile(_Config, _Commands, Profiler) -> + ?ABORT("Unsupported profiler: ~s~n", [Profiler]). + +run_aux(BaseConfig, Commands) -> + %% Make sure crypto is running + case crypto:start() of + ok -> ok; + {error,{already_started,crypto}} -> ok + end, + + %% Make sure memoization server is running + case rmemo:start() of + {ok, _} -> ok; + {error, {already_started, _}} -> ok + end, + + %% Convert command strings to atoms + CommandAtoms = [list_to_atom(C) || C <- Commands], + + BaseConfig1 = init_config1(BaseConfig), %% Process each command, resetting any state between each one - rebar_core:process_commands(CommandAtoms, BaseConfig). + rebar_core:process_commands(CommandAtoms, BaseConfig1). %% %% print help/usage string %% help() -> OptSpecList = option_spec_list(), - getopt:usage(OptSpecList, "rebar", - "[var=value,...] ", - [{"var=value", "rebar global variables (e.g. force=1)"}, - {"command", "Command to run (e.g. compile)"}]). + rebar_getopt:usage(OptSpecList, "rebar", + "[var=value,...] ", + [{"var=value", "rebar global variables (e.g. force=1)"}, + {"command", "Command to run (e.g. compile)"}]), + + ?CONSOLE("To see a list of built-in commands, execute rebar -c.~n~n", []), + ?CONSOLE( + "Type 'rebar help ' for help on specific commands." + "~n~n", []), + ?CONSOLE( + "rebar allows you to abbreviate the command to run:~n" + "$ rebar co # same as rebar compile~n" + "$ rebar eu # same as rebar eunit~n" + "$ rebar g-d # same as rebar get-deps~n" + "$ rebar x eu # same as rebar xref eunit~n" + "$ rebar l-d # same as rebar list-deps~n" + "$ rebar l-d l-t # same as rebar list-deps list-templates~n" + "$ rebar list-d l-te # same as rebar list-deps list-templates~n" + "~n", []), + ?CONSOLE( + "Core rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + "Core command line options:~n" + " apps=app1,app2 (specify apps to process)~n" + " skip_apps=app1,app2 (specify apps to skip)~n", + [ + {recursive_cmds, []}, + {require_erts_vsn, ".*"}, + {require_otp_vsn, ".*"}, + {require_min_otp_vsn, ".*"}, + {lib_dirs, []}, + {sub_dirs, ["dir1", "dir2"]}, + {plugins, [plugin1, plugin2]}, + {plugin_dir, "some_other_directory"}, + {pre_hooks, [{clean, "./prepare_package_files.sh"}, + {"linux", compile, "c_src/build_linux.sh"}, + {compile, "escript generate_headers"}, + {compile, "escript check_headers"}]}, + {post_hooks, [{clean, "touch file1.out"}, + {"freebsd", compile, "c_src/freebsd_tweaks.sh"}, + {eunit, "touch file2.out"}, + {compile, "touch postcompile.out"}]} + ]). %% %% 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)), - - %% 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 rebar_getopt:parse(OptSpecList, RawArgs) of + {ok, Args} -> + Args; {error, {Reason, Data}} -> ?ERROR("~s ~p~n~n", [Reason, Data]), help(), - halt(1) + 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)), + + %% Setup flag to enable recursive application of commands + Config4 = rebar_config:set_xconf(Config3, recursive, + proplists:get_bool(recursive, Options)), + + %% Set global variables based on getopt options + Config5 = set_global_flag(Config4, Options, force), + Config6 = case proplists:get_value(jobs, Options, ?DEFAULT_JOBS) of + ?DEFAULT_JOBS -> + Config5; + Jobs -> + rebar_config:set_global(Config5, 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. + {Config7, RawCmds} = filter_flags(Config6, NonOptArgs, []), + {Config7, unabbreviate_command_names(RawCmds)}. + %% %% set log level based on getopt option %% -set_log_level(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). +set_log_level(Config, Options) -> + {IsVerbose, Level} = + case proplists:get_bool(quiet, Options) of + true -> + {false, rebar_log:error_level()}; + false -> + DefaultLevel = rebar_log:default_level(), + case proplists:get_all_values(verbose, Options) of + [] -> + {false, DefaultLevel}; + Verbosities -> + {true, DefaultLevel + lists:last(Verbosities)} + end + end, + + case IsVerbose of + true -> + Config1 = rebar_config:set_xconf(Config, is_verbose, true), + rebar_config:set_global(Config1, verbose, Level); + false -> + rebar_config:set_global(Config, verbose, Level) + end. %% %% show version information and halt @@ -213,14 +374,14 @@ %% %% 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 @@ -233,7 +394,7 @@ [] -> ?CONSOLE("No command to run specified!~n",[]), help(), - halt(1); + rebar_utils:delayed_halt(1); _ -> ok end. @@ -252,71 +413,115 @@ %% commands() -> S = <<" -clean Clean -compile Compile sources +clean Clean +compile Compile sources + +escriptize Generate escript archive -create template= [var=foo,...] Create skel based on template and vars -create-app [appid=myapp] Create simple app skel -create-node [nodeid=mynode] Create simple node skel -list-templates List available templates - -doc Generate Erlang program documentation - -check-deps Display to be fetched dependencies -get-deps Fetch dependencies -update-deps Update fetched dependencies -delete-deps Delete fetched dependencies -list-deps List dependencies +create template= [var=foo,...] Create skel based on template and vars +create-app [appid=myapp] Create simple app skel +create-lib [libid=mylib] Create simple lib skel +create-node [nodeid=mynode] Create simple node skel +list-templates List available templates + +doc Generate Erlang program documentation + +prepare-deps Run 'rebar -r get-deps compile' +refresh-deps Run 'rebar -r update-deps compile' + +check-deps Display to be fetched dependencies +get-deps Fetch dependencies +update-deps Update fetched dependencies +delete-deps Delete fetched dependencies +list-deps List dependencies -generate [dump_spec=0/1] Build release with reltool -overlay Run reltool overlays only +generate [dump_spec=0/1] Build release with reltool +overlay Run reltool overlays only generate-upgrade previous_release=path Build an upgrade package generate-appups previous_release=path Generate appup files -eunit [suite=foo] Run eunit [test/foo_tests.erl] tests -ct [suites=] [case=] Run common_test suites in ./test +eunit [suite[s]=foo] Run EUnit tests in foo.erl and + test/foo_tests.erl + [suite[s]=foo] [test[s]=bar] Run specific EUnit tests [first test + name starting with 'bar' in foo.erl + and test/foo_tests.erl] + [test[s]=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. + [random_suite_order=true] Run tests in a random order, either + [random_suite_order=Seed] with a random seed for the PRNG, or a + specific one. + +ct [suite[s]= [group[s]= [case[s]=]]] Run common_test suites + +qc Test QuickCheck properties + +xref Run cross reference analysis + +dialyze Analyze the code for discrepancies +build-plt Build project-specific PLT +check-plt Check the PLT for consistency and + rebuild it if it is not up-to-date +delete-plt Delete project-specific PLT -xref Run cross reference analysis +shell Start a shell similar to + 'erl -pa ebin -pa deps/*/ebin' -help Show the program options -version Show version information +help Show the program options +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]), - VerboseHelp = "Verbosity level (-v, -vv, -vvv, --verbose 3). Default: 0", [ %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} {help, $h, "help", undefined, "Show the program options"}, {commands, $c, "commands", undefined, "Show available commands"}, - {verbose, $v, "verbose", integer, VerboseHelp}, + {verbose, $v, "verbose", integer, "Verbosity level (-v, -vv)"}, + {quiet, $q, "quiet", boolean, "Quiet, only print error messages"}, {version, $V, "version", undefined, "Show version information"}, {force, $f, "force", undefined, "Force"}, {defines, $D, undefined, string, "Define compiler macro"}, {jobs, $j, "jobs", integer, JobsHelp}, {config, $C, "config", string, "Rebar config file to use"}, - {profile, $p, "profile", undefined, "Profile this run of rebar"} + {profile, $p, "profile", undefined, + "Profile this run of rebar. Via profiler= you can optionally select " + "either fprof (default) or eflame. The result can be found in " + "fprof.analysis or eflame.svg."}, + {keep_going, $k, "keep-going", undefined, + "Keep running after a command fails"}, + {recursive, $r, "recursive", boolean, + "Apply all commands recursively. Alternatively, you can selectively" + " configure what other commands in addition to the always-recursive" + " ones (compile, *-deps) should also be applied recursively." + " For example, to make 'eunit' recursive, add {recursive_cmds, [eunit]}" + " to rebar.config."} ]. %% %% 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 @@ -325,18 +530,47 @@ _ -> 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"]. + [ + "build-plt", + "check-deps", + "check-plt", + "clean", + "compile", + "create", + "create-app", + "create-lib", + "create-node", + "ct", + "delete-plt", + "delete-deps", + "dialyze", + "doc", + "eunit", + "escriptize", + "generate", + "generate-appups", + "generate-upgrade", + "get-deps", + "help", + "list-deps", + "list-templates", + "prepare-deps", + "qc", + "refresh-deps", + "update-deps", + "overlay", + "shell", + "version", + "xref" + ]. unabbreviate_command_names([]) -> []; diff -Nru rebar-2.0.0/src/rebar_erlc_compiler.erl rebar-2.6.0/src/rebar_erlc_compiler.erl --- rebar-2.0.0/src/rebar_erlc_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_erlc_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -29,10 +29,23 @@ -export([compile/2, clean/2]). --export([doterl_compile/2, - doterl_compile/3]). +%% for internal use only +-export([test_compile/3, + info/2]). -include("rebar.hrl"). +-include_lib("stdlib/include/erl_compile.hrl"). + +-define(ERLCINFO_VSN, 2). +-define(ERLCINFO_FILE, "erlcinfo"). +-type erlc_info_v() :: {digraph:vertex(), term()} | 'false'. +-type erlc_info_e() :: {digraph:vertex(), digraph:vertex()}. +-type erlc_info() :: {list(erlc_info_v()), list(erlc_info_e()), list(string())}. +-record(erlcinfo, + { + vsn = ?ERLCINFO_VSN :: pos_integer(), + info = {[], [], []} :: erlc_info() + }). %% =================================================================== %% Public API @@ -68,7 +81,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,188 +100,444 @@ 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\$"), + MibFiles = rebar_utils:find_files_by_ext("mibs", ".mib"), MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles], rebar_file_utils:delete_each( [filename:join(["include",MIB++".hrl"]) || MIB <- MIBs]), lists:foreach(fun(F) -> ok = rebar_file_utils:rm_rf(F) end, ["ebin/*.beam", "priv/mibs/*.bin"]), - YrlFiles = rebar_utils:find_files("src", "^.*\\.[x|y]rl\$"), + YrlFiles = rebar_utils:find_files_by_ext("src", ".[x|y]rl"), rebar_file_utils:delete_each( [ binary_to_list(iolist_to_binary(re:replace(F, "\\.[x|y]rl$", ".erl"))) || F <- YrlFiles ]), + %% Delete the build graph, if any + rebar_file_utils:rm_rf(erlcinfo_file()), + %% Erlang compilation is recursive, so it's possible that we have a nested %% directory structure in ebin with .beam files within. As such, we want %% to scan whatever is left in the ebin/ directory for sub-dirs which %% satisfy our criteria. - BeamFiles = rebar_utils:find_files("ebin", "^.*\\.beam\$"), + BeamFiles = rebar_utils:find_files_by_ext("ebin", ".beam"), rebar_file_utils:delete_each(BeamFiles), 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. + TestErls = rebar_utils:find_files_by_ext("test", ".erl"), + + ErlOpts = rebar_utils:erl_opts(Config), + {Config1, ErlOpts1} = test_compile_config_and_opts(Config, ErlOpts, Cmd), + + %% 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. + SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts1)), + SrcErls = lists:foldl( + fun(Dir, Acc) -> + Files = rebar_utils:find_files_by_ext(Dir, ".erl"), + lists:append(Acc, Files) + end, [], SrcDirs), + + %% If it is not the first time rebar eunit or rebar qc 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(Config1, OutDir, TestErls, ErlOpts1), + + {ok, SrcErls}. %% =================================================================== -%% .erl Compilation API (externally used by only eunit) +%% Internal functions %% =================================================================== --spec doterl_compile(Config::rebar_config:config(), - OutDir::file:filename()) -> 'ok'. +info(help, compile) -> + info_help("Build *.erl, *.yrl, *.xrl, and *.mib sources"); +info(help, clean) -> + info_help("Delete *.erl, *.yrl, *.xrl, and *.mib build results"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n", + [ + Description, + {erl_opts, [no_debug_info, + {i, "myinclude"}, + {src_dirs, ["src", "src2", "src3"]}, + {platform_define, + "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'}, + {platform_define, "(linux|freebsd)", 'BACKLOG', 128}, + {platform_define, "R13", 'old_inets'}]}, + {erl_first_files, ["src/mymib1.erl", "src/mymib2.erl"]}, + {mib_opts, []}, + {mib_first_files, []}, + {xrl_opts, []}, + {xrl_first_files, []}, + {yrl_opts, []}, + {yrl_first_files, []} + ]). + +test_compile_config_and_opts(Config, ErlOpts, Cmd) -> + {Config1, TriqOpts} = triq_opts(Config), + {Config2, PropErOpts} = proper_opts(Config1), + {Config3, EqcOpts} = eqc_opts(Config2), + + %% NOTE: For consistency, all *_first_files lists should be + %% retrieved via rebar_config:get_local. Right now + %% erl_first_files, eunit_first_files, and qc_first_files use + %% rebar_config:get_list and are inherited, but xrl_first_files + %% and yrl_first_files use rebar_config:get_local. Inheritance of + %% *_first_files is questionable as the file would need to exist + %% in all project directories for it to work. + OptsAtom = list_to_atom(Cmd ++ "_compile_opts"), + TestOpts = rebar_config:get_list(Config3, OptsAtom, []), + Opts0 = [{d, 'TEST'}] ++ + ErlOpts ++ TestOpts ++ 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, []), + Config5 = rebar_config:set(Config4, erl_first_files, FirstErls), + {Config5, Opts}. + +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, []). + ErlOpts = rebar_utils:erl_opts(Config), + doterl_compile(Config, OutDir, [], ErlOpts). -doterl_compile(Config, OutDir, MoreSources) -> - FirstErls = rebar_config:get_list(Config, erl_first_files, []), - ErlOpts = erl_opts(Config), +doterl_compile(Config, OutDir, MoreSources, ErlOpts) -> ?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)), - RestErls = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources, - not lists:member(Source, FirstErls)], - - %% Split RestErls so that parse_transforms and behaviours are instead added - %% to erl_first_files, parse transforms first. - %% This should probably be somewhat combined with inspect_epp - [ParseTransforms, Behaviours, OtherErls] = - lists:foldl(fun(F, [A, B, C]) -> - case compile_priority(F) of - parse_transform -> - [[F | A], B, C]; - behaviour -> - [A, [F | B], C]; - callback -> - [A, [F | B], C]; - _ -> - [A, B, [F | C]] - end - end, [[], [], []], RestErls), - - NewFirstErls = FirstErls ++ ParseTransforms ++ Behaviours, + SrcDirs = rebar_utils:src_dirs(proplists:append_values(src_dirs, ErlOpts)), + AllErlFiles = gather_src(SrcDirs, []) ++ MoreSources, %% Make sure that ebin/ exists and is on the path ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")), CurrPath = code:get_path(), true = code:add_path(filename:absname("ebin")), - rebar_base_compiler:run(Config, NewFirstErls, OtherErls, - fun(S, C) -> - internal_erl_compile(S, C, OutDir, ErlOpts) - end), - true = code:set_path(CurrPath), - ok. + OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir), + G = init_erlcinfo(proplists:get_all_values(i, ErlOpts), AllErlFiles), + NeededErlFiles = needed_files(G, OutDir1, AllErlFiles), + {ErlFirstFiles, ErlOptsFirst} = erl_first_files(Config, + ErlOpts, + NeededErlFiles), + {DepErls, OtherErls} = + lists:partition( + fun(Source) -> + digraph:in_degree(G, Source) > 0 + end, + [F || F <- NeededErlFiles, not lists:member(F, ErlFirstFiles)]), + DepErlsOrdered = digraph_utils:topsort(digraph_utils:subgraph(G, DepErls)), + FirstErls = ErlFirstFiles ++ lists:reverse(DepErlsOrdered), + ?DEBUG("Files to compile first: ~p~n", [FirstErls]), + + rebar_base_compiler:run( + Config, FirstErls, OtherErls, + fun(S, C) -> + ErlOpts1 = case lists:member(S, ErlFirstFiles) of + true -> ErlOptsFirst; + false -> ErlOpts + end, + internal_erl_compile(C, S, OutDir1, ErlOpts1) + end), + true = rebar_utils:cleanup_code_path(CurrPath), + ok. -%% =================================================================== -%% 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(), ...]. -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()]}. -inspect(Source, IncludePath) -> - ModuleDefault = filename:basename(Source, ".erl"), - case epp:open(Source, IncludePath) of - {ok, Epp} -> - inspect_epp(Epp, Source, ModuleDefault, []); - {error, Reason} -> - ?DEBUG("Failed to inspect ~s: ~p\n", [Source, Reason]), - {ModuleDefault, []} - end. - --spec inspect_epp(Epp::pid(), Source::file:filename(), Module::file:filename(), - Includes::[string()]) -> {string(), [string()]}. -inspect_epp(Epp, Source, Module, Includes) -> - case epp:parse_erl_form(Epp) of - {ok, {attribute, _, module, ModInfo}} -> - case ModInfo of - %% Typical module name, single atom - ActualModule when is_atom(ActualModule) -> - ActualModuleStr = atom_to_list(ActualModule); - %% Packag-ized module name, list of atoms - ActualModule when is_list(ActualModule) -> - ActualModuleStr = string:join([atom_to_list(P) || - P <- ActualModule], "."); - %% Parameterized module name, single atom - {ActualModule, _} when is_atom(ActualModule) -> - ActualModuleStr = atom_to_list(ActualModule); - %% Parameterized and packagized module name, list of atoms - {ActualModule, _} when is_list(ActualModule) -> - ActualModuleStr = string:join([atom_to_list(P) || - P <- ActualModule], ".") +%% Get files which need to be compiled first, i.e. those specified in +%% erl_first_files and parse_transform options. Also produce specific +%% erl_opts for these first files, so that yet to be compiled parse +%% transformations are excluded from it. +erl_first_files(Config, ErlOpts, NeededErlFiles) -> + %% NOTE: rebar_config:get_local perhaps? + ErlFirstFilesConf = rebar_config:get_list(Config, erl_first_files, []), + NeededSrcDirs = lists:usort( + lists:map(fun filename:dirname/1, NeededErlFiles)), + %% NOTE: order of files here is important! + ErlFirstFiles = lists:filter( + fun(File) -> lists:member(File, NeededErlFiles) end, + ErlFirstFilesConf), + {ParseTransforms, ParseTransformsErls} = + lists:unzip( + lists:flatmap( + fun(PT) -> + PTerls = [filename:join(Dir, module_to_erl(PT)) + || Dir <- NeededSrcDirs], + [{PT, PTerl} || PTerl <- PTerls, + lists:member(PTerl, NeededErlFiles)] end, - inspect_epp(Epp, Source, ActualModuleStr, Includes); - {ok, {attribute, 1, file, {Module, 1}}} -> - inspect_epp(Epp, Source, Module, Includes); - {ok, {attribute, 1, file, {Source, 1}}} -> - inspect_epp(Epp, Source, Module, Includes); - {ok, {attribute, 1, file, {IncFile, 1}}} -> - inspect_epp(Epp, Source, Module, [IncFile | Includes]); - {eof, _} -> - epp:close(Epp), - {Module, Includes}; - _ -> - inspect_epp(Epp, Source, Module, Includes) + proplists:get_all_values(parse_transform, ErlOpts))), + ErlOptsFirst = lists:filter( + fun ({parse_transform, PT}) -> + not lists:member(PT, ParseTransforms); + (_) -> true + end, + ErlOpts), + {ErlFirstFiles ++ ParseTransformsErls, ErlOptsFirst}. + +%% Get subset of SourceFiles which need to be recompiled, respecting +%% dependencies induced by given graph G. +needed_files(G, OutDir, SourceFiles) -> + lists:filter( + fun(Source) -> + Target = target_base(OutDir, Source) ++ ".beam", + digraph:vertex(G, Source) > + {Source, filelib:last_modified(Target)} + end, SourceFiles). + +target_base(OutDir, Source) -> + filename:join(OutDir, filename:basename(Source, ".erl")). + +erlcinfo_file() -> + filename:join([rebar_utils:get_cwd(), ".rebar", ?ERLCINFO_FILE]). + +%% Get dependency graph of given Erls files and their dependencies +%% (header files, parse transforms, behaviours etc.) located in their +%% directories or given InclDirs. Note that last modification times +%% stored in vertices already respect +%% dependencies induced by given graph G. +init_erlcinfo(InclDirs, Erls) -> + G = digraph:new([acyclic]), + try restore_erlcinfo(G, InclDirs) + catch + _:_ -> + ?WARN("Failed to restore ~s file. Discarding it.~n", + [erlcinfo_file()]), + ok = file:delete(erlcinfo_file()) + end, + Dirs = source_and_include_dirs(InclDirs, Erls), + Modified = lists:foldl(update_erlcinfo_fun(G, Dirs), false, Erls), + if Modified -> store_erlcinfo(G, InclDirs); not Modified -> ok end, + G. + +source_and_include_dirs(InclDirs, Erls) -> + SourceDirs = lists:map(fun filename:dirname/1, Erls), + lists:usort(["include" | InclDirs ++ SourceDirs]). + +update_erlcinfo_fun(G, Dirs) -> + fun(Erl, Modified) -> + case update_erlcinfo(G, Dirs, Erl) of + modified -> true; + unmodified -> Modified + end end. --spec needs_compile(Source::file:filename(), Target::file:filename(), - Hrls::[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) -> - %% Determine the target name and includes list by inspecting the source file - {Module, Hrls} = inspect(Source, include_path(Source, Config)), - - %% Construct the target filename - Target = filename:join([Outdir | string:tokens(Module, ".")]) ++ ".beam", - ok = filelib:ensure_dir(Target), - - %% If the file needs compilation, based on last mod date of includes or - %% the target - case needs_compile(Source, Target, Hrls) of - true -> - Opts = [{outdir, filename:dirname(Target)}] ++ - ErlOpts ++ [{i, "include"}, report], - case compile:file(Source, Opts) of - {ok, _} -> - ok; +update_erlcinfo(G, Dirs, Source) -> + case digraph:vertex(G, Source) of + {_, LastUpdated} -> + case filelib:last_modified(Source) of + 0 -> + %% The file doesn't exist anymore, + %% erase it from the graph. + %% All the edges will be erased automatically. + ?WARN("File missing. Remove from dep graph:~n~s~n", + [Source]), + digraph:del_vertex(G, Source), + modified; + LastModified when LastUpdated < LastModified -> + modify_erlcinfo(G, Source, LastModified, Dirs); _ -> - ?FAIL + Modified = lists:foldl( + update_erlcinfo_fun(G, Dirs), + false, digraph:out_neighbours(G, Source)), + MaxModified = update_max_modified_deps(G, Source), + case Modified orelse MaxModified > LastUpdated of + true -> modified; + false -> unmodified + end end; false -> - skipped + modify_erlcinfo(G, Source, filelib:last_modified(Source), Dirs) + end. + +update_max_modified_deps(G, Source) -> + MaxModified = lists:max( + lists:map( + fun(File) -> + {_, MaxModified} = digraph:vertex(G, File), + MaxModified + end, + [Source|digraph:out_neighbours(G, Source)])), + digraph:add_vertex(G, Source, MaxModified), + MaxModified. + +modify_erlcinfo(_G, Source, 0, _Dirs) -> + ?WARN("modify_erlcinfo: failed to open file:~n~s~n", [Source]), + unmodified; +modify_erlcinfo(G, Source, LastModified, Dirs) -> + {ok, Fd} = file:open(Source, [read]), + Incls = parse_attrs(Fd, []), + AbsIncls = expand_file_names(Incls, Dirs), + ok = file:close(Fd), + digraph:add_vertex(G, Source, LastModified), + digraph:del_edges(G, digraph:out_edges(G, Source)), + lists:foreach( + fun(Incl) -> + update_erlcinfo(G, Dirs, Incl), + digraph:add_edge(G, Source, Incl) + end, AbsIncls), + modified. + +restore_erlcinfo(G, InclDirs) -> + case file:read_file(erlcinfo_file()) of + {ok, Data} -> + %% Since externally passed InclDirs can influence erlcinfo + %% graph (see modify_erlcinfo), we have to check here that + %% they didn't change. + #erlcinfo{vsn=?ERLCINFO_VSN, info={Vs, Es, InclDirs}} = + binary_to_term(Data), + lists:foreach( + fun({V, LastUpdated}) -> + digraph:add_vertex(G, V, LastUpdated) + end, Vs), + lists:foreach( + fun({_, V1, V2, _}) -> + digraph:add_edge(G, V1, V2) + end, Es); + {error, _} -> + ok + end. + +store_erlcinfo(G, InclDirs) -> + Vs = lists:map(fun(V) -> digraph:vertex(G, V) end, digraph:vertices(G)), + Es = lists:map(fun(E) -> digraph:edge(G, E) end, digraph:edges(G)), + File = erlcinfo_file(), + ok = filelib:ensure_dir(File), + Info = #erlcinfo{info={Vs, Es, InclDirs}}, + Data = term_to_binary(Info, [{compressed, 2}]), + ok = file:write_file(File, Data). + +%% NOTE: If, for example, one of the entries in Files refers to +%% gen_server.erl, that entry will be dropped. It is dropped because +%% such an entry usually refers to the beam file, and we don't pass a +%% list of OTP src dirs for finding gen_server.erl's full path. Also, +%% if gen_server.erl was modified, it's not rebar's task to compile a +%% new version of the beam file. Therefore, it's reasonable to drop +%% such entries. Also see process_attr(behaviour, Form, Includes). +-spec expand_file_names([file:filename()], + [file:filename()]) -> [file:filename()]. +expand_file_names(Files, Dirs) -> + %% We check if Files exist by itself or within the directories + %% listed in Dirs. + %% Return the list of files matched. + lists:flatmap( + fun(Incl) -> + case filelib:is_regular(Incl) of + true -> + [Incl]; + false -> + lists:flatmap( + fun(Dir) -> + FullPath = filename:join(Dir, Incl), + case filelib:is_regular(FullPath) of + true -> + [FullPath]; + false -> + [] + end + end, Dirs) + end + end, Files). + +-spec internal_erl_compile( + rebar_config:config(), + file:filename(), + file:filename(), + list()) -> ok | {ok, any()} | {error, any(), any()}. +internal_erl_compile(Config, Source, OutDir, ErlOpts) -> + ok = filelib:ensure_dir(OutDir), + Opts = [{outdir, OutDir}] ++ ErlOpts ++ [{i, "include"}, return], + case compile:file(Source, Opts) of + {ok, _Mod} -> + ok; + {ok, _Mod, Ws} -> + rebar_base_compiler:ok_tuple(Config, Source, Ws); + {error, Es, Ws} -> + rebar_base_compiler:error_tuple(Config, Source, Es, Ws, Opts) 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")), @@ -277,7 +546,14 @@ case snmpc:compile(Source, Opts) of {ok, _} -> Mib = filename:rootname(Target), - ok = snmpc:mib_to_hrl(Mib), + MibToHrlOpts = + case proplists:get_value(verbosity, Opts, undefined) of + undefined -> + #options{specific = []}; + Verbosity -> + #options{specific = [{verbosity, Verbosity}]} + end, + ok = snmpc:mib_to_hrl(Mib, Mib, MibToHrlOpts), Hrl_filename = Mib ++ ".hrl", rebar_file_utils:mv(Hrl_filename, "include"), ok; @@ -285,118 +561,157 @@ ?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) -> - case needs_compile(Source, Target, []) of +-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) of + case Mod:file(Source, Opts ++ [{return, true}]) of {ok, _} -> ok; - _X -> - ?FAIL + {ok, _Mod, Ws} -> + rebar_base_compiler:ok_tuple(Config, Source, Ws); + {error, Es, Ws} -> + rebar_base_compiler:error_tuple(Config, Source, + Es, Ws, Opts) end; false -> skipped end. +needs_compile(Source, Target) -> + filelib:last_modified(Source) > filelib:last_modified(Target). + gather_src([], Srcs) -> Srcs; gather_src([Dir|Rest], Srcs) -> - gather_src(Rest, Srcs ++ rebar_utils:find_files(Dir, ".*\\.erl\$")). + gather_src( + Rest, Srcs ++ rebar_utils:find_files_by_ext(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'. -compile_priority(File) -> - case epp_dodger:parse_file(File) of - {error, _} -> - normal; % couldn't parse the file, default priority - {ok, Trees} -> - F2 = fun({tree,arity_qualifier,_, - {arity_qualifier,{tree,atom,_,behaviour_info}, - {tree,integer,_,1}}}, _) -> - behaviour; - ({tree,arity_qualifier,_, - {arity_qualifier,{tree,atom,_,parse_transform}, - {tree,integer,_,2}}}, _) -> - parse_transform; - (_, Acc) -> - Acc - end, - - F = fun({tree, attribute, _, - {attribute, {tree, atom, _, export}, - [{tree, list, _, {list, List, none}}]}}, Acc) -> - lists:foldl(F2, Acc, List); - ({tree, attribute, _, - {attribute, {tree, atom, _, callback},_}}, _Acc) -> - callback; - (_, Acc) -> - Acc - end, - - lists:foldl(F, normal, Trees) +parse_attrs(Fd, Includes) -> + case io:parse_erl_form(Fd, "") of + {ok, Form, _Line} -> + case erl_syntax:type(Form) of + attribute -> + NewIncludes = process_attr(Form, Includes), + parse_attrs(Fd, NewIncludes); + _ -> + parse_attrs(Fd, Includes) + end; + {eof, _} -> + Includes; + _Err -> + parse_attrs(Fd, Includes) 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) +process_attr(Form, Includes) -> + AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)), + process_attr(AttrName, Form, Includes). + +process_attr(import, Form, Includes) -> + case erl_syntax_lib:analyze_import_attribute(Form) of + {Mod, _Funs} -> + [module_to_erl(Mod)|Includes]; + Mod -> + [module_to_erl(Mod)|Includes] + end; +process_attr(file, Form, Includes) -> + {File, _} = erl_syntax_lib:analyze_file_attribute(Form), + [File|Includes]; +process_attr(include, Form, Includes) -> + [FileNode] = erl_syntax:attribute_arguments(Form), + File = erl_syntax:string_value(FileNode), + [File|Includes]; +process_attr(include_lib, Form, Includes) -> + [FileNode] = erl_syntax:attribute_arguments(Form), + RawFile = erl_syntax:string_value(FileNode), + maybe_expand_include_lib_path(RawFile) ++ Includes; +process_attr(behaviour, Form, Includes) -> + [FileNode] = erl_syntax:attribute_arguments(Form), + File = module_to_erl(erl_syntax:atom_value(FileNode)), + [File|Includes]; +process_attr(compile, Form, Includes) -> + [Arg] = erl_syntax:attribute_arguments(Form), + case erl_syntax:concrete(Arg) of + {parse_transform, Mod} -> + [module_to_erl(Mod)|Includes]; + {core_transform, Mod} -> + [module_to_erl(Mod)|Includes]; + L when is_list(L) -> + lists:foldl( + fun({parse_transform, Mod}, Acc) -> + [module_to_erl(Mod)|Acc]; + ({core_transform, Mod}, Acc) -> + [module_to_erl(Mod)|Acc]; + (_, Acc) -> + Acc + end, Includes, L); + _ -> + Includes end; -filter_defines([{platform_define, ArchRegex, Key, Value} | Rest], Acc) -> - case rebar_utils:is_arch(ArchRegex) of +process_attr(_, _Form, Includes) -> + Includes. + +module_to_erl(Mod) -> + atom_to_list(Mod) ++ ".erl". + +%% Given the filename from an include_lib attribute, if the path +%% exists, return unmodified, or else get the absolute ERL_LIBS +%% path. +maybe_expand_include_lib_path(File) -> + case filelib:is_regular(File) of true -> - filter_defines(Rest, [{d, Key, Value} | Acc]); + [File]; false -> - filter_defines(Rest, Acc) - end; -filter_defines([Opt | Rest], Acc) -> - filter_defines(Rest, [Opt | Acc]). + expand_include_lib_path(File) + end. + +%% Given a path like "stdlib/include/erl_compile.hrl", return +%% "OTP_INSTALL_DIR/lib/erlang/lib/stdlib-x.y.z/include/erl_compile.hrl". +%% Usually a simple [Lib, SubDir, File1] = filename:split(File) should +%% work, but to not crash when an unusual include_lib path is used, +%% utilize more elaborate logic. +expand_include_lib_path(File) -> + File1 = filename:basename(File), + [Lib | Parts] = filename:split(filename:dirname(File)), + SubDir = case Parts of + [] -> % prevent function clause error + []; + _ -> + filename:join(Parts) + end, + case code:lib_dir(list_to_atom(Lib), list_to_atom(SubDir)) of + {error, bad_name} -> []; + Dir -> [filename:join(Dir, File1)] + end. %% %% 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 -Nru rebar-2.0.0/src/rebar_erlydtl_compiler.erl rebar-2.6.0/src/rebar_erlydtl_compiler.erl --- rebar-2.0.0/src/rebar_erlydtl_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_erlydtl_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -31,7 +31,11 @@ %% to ebin/*_dtl.beam. %% %% Configuration options should be placed in rebar.config under -%% 'erlydtl_opts'. Available options include: +%% 'erlydtl_opts'. It can be a list of name-value tuples or a list of +%% lists of name-value tuples if you have multiple template directories +%% that need to have different settings (see example below). +%% +%% Available options include: %% %% doc_root: where to find templates to compile %% "templates" by default @@ -45,6 +49,9 @@ %% module_ext: characters to append to the template's module name %% "_dtl" by default %% +%% recursive: boolean that determines if doc_root(s) need to be +%% scanned recursively for matching template file names +%% (default: true). %% For example, if you had: %% /t_src/ %% base.html @@ -70,10 +77,28 @@ %% {source_ext, ".dtl"}, %% {module_ext, "_dtl"} %% ]}. +%% +%% The following example will compile the following templates: +%% "src/*.dtl" files into "ebin/*_dtl.beam" and +%% "templates/*.html" into "ebin/*.beam". Note that any tuple option +%% (such as 'out_dir') in the outer list is added to each inner list: +%% {erlydtl_opts, [ +%% {out_dir, "ebin"}, +%% {recursive, false}, +%% [ +%% {doc_root, "src"}, {module_ext, "_dtl"} +%% ], +%% [ +%% {doc_root, "templates"}, {module_ext, ""}, {source_ext, ".html"} +%% ] +%% ]}. -module(rebar_erlydtl_compiler). -export([compile/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -81,26 +106,60 @@ %% =================================================================== compile(Config, _AppFile) -> - DtlOpts = erlydtl_opts(Config), + MultiDtlOpts = erlydtl_opts(Config), OrigPath = code:get_path(), - true = code:add_path(filename:join(rebar_utils:get_cwd(), "ebin")), - Result = rebar_base_compiler:run(Config, [], + true = code:add_path(rebar_utils:ebin_dir()), + + Result = lists:foldl(fun(DtlOpts, _) -> + rebar_base_compiler:run(Config, [], option(doc_root, DtlOpts), option(source_ext, DtlOpts), option(out_dir, DtlOpts), option(module_ext, DtlOpts) ++ ".beam", - fun compile_dtl/3, - [{check_last_mod, false}]), - true = code:set_path(OrigPath), - Result. + fun(S, T, C) -> + compile_dtl(C, S, T, DtlOpts) + end, + [{check_last_mod, false}, + {recursive, option(recursive, DtlOpts)}]) + end, ok, MultiDtlOpts), + true = rebar_utils:cleanup_code_path(OrigPath), + Result. %% =================================================================== %% Internal functions %% =================================================================== +info(help, compile) -> + ?CONSOLE( + "Build ErlyDtl (*.dtl) sources.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + {erlydtl_opts, [{doc_root, "templates"}, + {out_dir, "ebin"}, + {source_ext, ".dtl"}, + {module_ext, "_dtl"}, + {recursive, true}]} + ]). + erlydtl_opts(Config) -> - rebar_config:get(Config, erlydtl_opts, []). + Opts = rebar_config:get(Config, erlydtl_opts, []), + Tuples = [{K,V} || {K,V} <- Opts], + case [L || L <- Opts, is_list(L), not io_lib:printable_list(L)] of + [] -> + [lists:keysort(1, Tuples)]; + Lists -> + lists:map( + fun(L) -> + lists:keysort(1, + lists:foldl( + fun({K,T}, Acc) -> + lists:keystore(K, 1, Acc, {K, T}) + end, Tuples, L)) + end, Lists) + end. option(Opt, DtlOpts) -> proplists:get_value(Opt, DtlOpts, default(Opt)). @@ -109,63 +168,90 @@ default(out_dir) -> "ebin"; default(source_ext) -> ".dtl"; default(module_ext) -> "_dtl"; -default(custom_tags_dir) -> "". +default(custom_tags_dir) -> ""; +default(compiler_options) -> [return]; +default(recursive) -> true. -compile_dtl(Source, Target, Config) -> +compile_dtl(Config, Source, Target, DtlOpts) -> case code:which(erlydtl) of non_existing -> ?ERROR("~n===============================================~n" " You need to install erlydtl to compile DTL templates~n" " Download the latest tarball release from github~n" - " http://code.google.com/p/erlydtl/~n" + " https://github.com/erlydtl/erlydtl/releases~n" " and install it into your erlang library dir~n" "===============================================~n~n", []), ?FAIL; _ -> - case needs_compile(Source, Target, Config) of + case needs_compile(Source, Target, DtlOpts) of true -> - do_compile(Source, Target, Config); + do_compile(Config, Source, Target, DtlOpts); false -> skipped end end. -do_compile(Source, Target, Config) -> +do_compile(Config, Source, Target, DtlOpts) -> %% TODO: Check last mod on target and referenced DTLs here.. - DtlOpts = erlydtl_opts(Config), + + %% erlydtl >= 0.8.1 does not use the extra indirection using the + %% compiler_options. Kept for backward compatibility with older + %% versions of erlydtl. + CompilerOptions = option(compiler_options, DtlOpts), + + Sorted = proplists:unfold( + lists:sort( + [{out_dir, option(out_dir, DtlOpts)}, + {doc_root, option(doc_root, DtlOpts)}, + {custom_tags_dir, option(custom_tags_dir, DtlOpts)}, + {compiler_options, CompilerOptions} + |CompilerOptions])), + %% ensure that doc_root and out_dir are defined, %% using defaults if necessary - Opts = [{out_dir, option(out_dir, DtlOpts)}, - {doc_root, option(doc_root, DtlOpts)}, - {custom_tags_dir, option(custom_tags_dir, DtlOpts)}, - report, return], + Opts = lists:ukeymerge(1, DtlOpts, Sorted), + ?INFO("Compiling \"~s\" -> \"~s\" with options:~n ~s~n", + [Source, Target, io_lib:format("~p", [Opts])]), case erlydtl:compile(Source, module_name(Target), - Opts++DtlOpts) of - ok -> ok; - Reason -> - ?ERROR("Compiling template ~s failed:~n ~p~n", - [Source, Reason]), - ?FAIL + Opts) of + ok -> + ok; + {ok, _Mod} -> + ok; + {ok, _Mod, Ws} -> + rebar_base_compiler:ok_tuple(Config, Source, Ws); + {ok, _Mod, _Bin, Ws} -> + rebar_base_compiler:ok_tuple(Config, Source, Ws); + error -> + rebar_base_compiler:error_tuple(Config, Source, [], [], Opts); + {error, {_File, _Msgs} = Error} -> + rebar_base_compiler:error_tuple(Config, Source, [Error], [], Opts); + {error, Msg} -> + Es = [{Source, [{erlydtl_parser, Msg}]}], + rebar_base_compiler:error_tuple(Config, Source, Es, [], Opts); + {error, Es, Ws} -> + rebar_base_compiler:error_tuple(Config, Source, Es, Ws, Opts) end. module_name(Target) -> F = filename:basename(Target), string:substr(F, 1, length(F)-length(".beam")). -needs_compile(Source, Target, Config) -> +needs_compile(Source, Target, DtlOpts) -> LM = filelib:last_modified(Target), LM < filelib:last_modified(Source) orelse lists:any(fun(D) -> LM < filelib:last_modified(D) end, - referenced_dtls(Source, Config)). + referenced_dtls(Source, DtlOpts)). -referenced_dtls(Source, Config) -> - Set = referenced_dtls1([Source], Config, +referenced_dtls(Source, DtlOpts) -> + DtlOpts1 = lists:keyreplace(doc_root, 1, DtlOpts, + {doc_root, filename:dirname(Source)}), + Set = referenced_dtls1([Source], DtlOpts1, sets:add_element(Source, sets:new())), sets:to_list(sets:del_element(Source, Set)). -referenced_dtls1(Step, Config, Seen) -> - DtlOpts = erlydtl_opts(Config), +referenced_dtls1(Step, DtlOpts, Seen) -> ExtMatch = re:replace(option(source_ext, DtlOpts), "\.", "\\\\\\\\.", [{return, list}]), @@ -173,8 +259,8 @@ AllRefs = lists:append( [begin - Cmd = lists:flatten(["grep -o [^\\\"]*", - ExtMatch, " ", F]), + Cmd = lists:flatten(["grep -o [^\\\"]*\\", + ExtMatch, "[^\\\"]* ", F]), case rebar_utils:sh(Cmd, ShOpts) of {ok, Res} -> string:tokens(Res, "\n"); @@ -184,10 +270,11 @@ end || F <- Step]), DocRoot = option(doc_root, DtlOpts), WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ], + ?DEBUG("All deps: ~p\n", [WithPaths]), Existing = [F || F <- WithPaths, filelib:is_regular(F)], New = sets:subtract(sets:from_list(Existing), Seen), case sets:size(New) of 0 -> Seen; - _ -> referenced_dtls1(sets:to_list(New), Config, + _ -> referenced_dtls1(sets:to_list(New), DtlOpts, sets:union(New, Seen)) end. diff -Nru rebar-2.0.0/src/rebar_escripter.erl rebar-2.6.0/src/rebar_escripter.erl --- rebar-2.0.0/src/rebar_escripter.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_escripter.erl 2015-06-19 16:14:28.000000000 +0000 @@ -29,6 +29,9 @@ -export([escriptize/2, clean/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). -include_lib("kernel/include/file.hrl"). @@ -36,10 +39,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,9 +55,17 @@ InclBeams = get_app_beams( rebar_config:get_local(Config, escript_incl_apps, []), []), + %% 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, + 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}} -> %% Archive was successfully created. Prefix that binary with our @@ -61,7 +73,10 @@ 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 -> @@ -79,23 +94,47 @@ %% 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 %% =================================================================== +info(help, escriptize) -> + info_help("Generate escript archive"); +info(help, clean) -> + info_help("Delete generated escript archive"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n", + [ + Description, + {escript_name, "application"}, + {escript_incl_apps, []}, + {escript_shebang, "#!/usr/bin/env escript\n"}, + {escript_comment, "%%\n"}, + {escript_emu_args, "%%! -pa application/application/ebin\n"} + ]). + get_app_beams([], Acc) -> Acc; get_app_beams([App | Rest], Acc) -> @@ -104,18 +143,55 @@ ?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. +get_extra(Config) -> + Extra = rebar_config:get_local(Config, escript_incl_extra, []), + lists:foldl(fun({Wildcard, Dir}, Files) -> + load_files(Wildcard, Dir) ++ Files + end, [], Extra). + load_files(Wildcard, Dir) -> - [read_file(Filename, Dir) || Filename <- filelib:wildcard(Wildcard, Dir)]. + load_files("", Wildcard, Dir). -read_file(Filename, Dir) -> - {Filename, file_contents(filename:join(Dir, Filename))}. +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 -Nru rebar-2.0.0/src/rebar_eunit.erl rebar-2.6.0/src/rebar_eunit.erl --- rebar-2.0.0/src/rebar_eunit.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_eunit.erl 2015-06-19 16:14:28.000000000 +0000 @@ -28,7 +28,7 @@ %% @doc rebar_eunit supports the following commands: %%
    %%
  • eunit - runs eunit tests
  • -%%
  • clean - remove .eunit directory
  • +%%
  • clean - remove ?EUNIT_DIR directory
  • %%
  • 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: %%
      %%
    • verbose=1 - show extra output from the eunit test
    • -%%
    • suite="foo"" - runs test/foo_tests.erl
    • +%%
    • +%% suites="foo,bar" - runs tests in foo.erl, test/foo_tests.erl and +%% tests in bar.erl, test/bar_tests.erl +%%
    • +%%
    • +%% 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 +%%
    • +%%
    • +%% 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 +%%
    • %%
    %% Additionally, for projects that have separate folders for the core %% implementation, and for the unit tests, then the following %% rebar.config option can be provided: -%% {eunit_compile_opts, [{src_dirs, ["dir"]}]}.. +%% {eunit_compile_opts, [{src_dirs, ["src", "dir"]}]}.. %% @copyright 2009, 2010 Dave Smith %% ------------------------------------------------------------------- -module(rebar_eunit). @@ -56,6 +69,11 @@ -export([eunit/2, clean/2]). +%% for internal use only +-export([info/2]). + +-dialyzer({no_missing_calls, pre15b02_eunit_primitive/3}). + -include("rebar.hrl"). -define(EUNIT_DIR, ".eunit"). @@ -65,81 +83,96 @@ %% =================================================================== 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 = ensure_dirs(), + %% Save code path + CodePath = setup_code_path(), + CompileOnly = rebar_config:get_global(Config, compile_only, false), + {ok, SrcErls} = rebar_erlc_compiler:test_compile(Config, "eunit", + ?EUNIT_DIR), + case CompileOnly of + "true" -> + true = rebar_utils:cleanup_code_path(CodePath), + ?CONSOLE("Compiled modules for eunit~n", []); + false -> + run_eunit(Config, CodePath, SrcErls) + end. - ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], TestErls)), - ok = rebar_file_utils:delete_each(lists:foldl(ToCleanUp, [], SrcErls)), +clean(_Config, _File) -> + rebar_file_utils:rm_rf(?EUNIT_DIR). - ok = rebar_file_utils:cp_r(SrcErls ++ TestErls, ?EUNIT_DIR), +%% =================================================================== +%% Internal functions +%% =================================================================== - %% 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), +info(help, eunit) -> + info_help("Run eunit tests"); +info(help, clean) -> + Description = ?FMT("Delete eunit test dir (~s)", [?EUNIT_DIR]), + info_help(Description). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + "Valid command line options:~n" + " suite[s]=\"foo,bar\" (Run tests in foo.erl, test/foo_tests.erl and~n" + " tests in bar.erl, test/bar_tests.erl)~n" + " test[s]=\"baz\" (For every existing suite, run the first test whose~n" + " name starts with bar and, if no such test exists,~n" + " run the test whose name starts with bar in the~n" + " suite's _tests module)~n" + " random_suite_order=true (Run tests in random order)~n" + " random_suite_order=Seed (Run tests in random order,~n" + " with the PRNG seeded with Seed)~n" + " compile_only=true (Compile but do not run tests)", + [ + Description, + {eunit_opts, []}, + {eunit_compile_opts, []}, + {eunit_first_files, []}, + {cover_enabled, false}, + {cover_print_enabled, false}, + {cover_export_enabled, false} + ]). +run_eunit(Config, CodePath, SrcErls) -> %% Build a list of all the .beams in ?EUNIT_DIR -- use this for %% cover and eunit testing. Normally you can just tell cover %% and/or eunit to scan the directory for you, but eunit does a %% code:purge in conjunction with that scan and causes any cover - %% compilation info to be lost. 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, AllBeamFiles), OtherBeamFiles = TestBeamFiles -- [filename:rootname(N) ++ "_tests.beam" || N <- AllBeamFiles], - ModuleBeamFiles = BeamFiles ++ OtherBeamFiles, - Modules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- ModuleBeamFiles], + ModuleBeamFiles = randomize_suites(Config, BeamFiles ++ OtherBeamFiles), + + %% Get matching tests and modules + AllModules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- AllBeamFiles], + {Tests, FilteredModules} = + get_tests_and_modules(Config, ModuleBeamFiles, AllModules), + SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], - {ok, CoverLog} = cover_init(Config, ModuleBeamFiles), + {ok, CoverLog} = rebar_cover_utils:init(Config, ModuleBeamFiles, + eunit_dir()), StatusBefore = status_before_eunit(), - EunitResult = perform_eunit(Config, Modules), - perform_cover(Config, Modules, SrcModules), + EunitResult = perform_eunit(Config, Tests), - cover_close(CoverLog), + rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules, + eunit_dir()), + rebar_cover_utils:close(CoverLog), case proplists:get_value(reset_after_eunit, get_eunit_opts(Config), true) of @@ -149,6 +182,10 @@ ok end, + %% Stop cover to clean the cover_server state. This is important if we want + %% eunit+cover to not slow down when analyzing many Erlang modules. + ok = rebar_cover_utils:exit(), + case EunitResult of ok -> ok; @@ -157,26 +194,289 @@ end, %% Restore code path - true = code:set_path(CodePath), + true = rebar_utils:cleanup_code_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. + +%% +%% == get matching tests == +%% +get_tests_and_modules(Config, ModuleBeamFiles, AllModules) -> + SelectedSuites = get_selected_suites(Config, AllModules), + {Tests, QualifiedTests} = get_qualified_and_unqualified_tests(Config), + Modules = get_test_modules(SelectedSuites, Tests, + QualifiedTests, ModuleBeamFiles), + FilteredModules = get_matching_modules(AllModules, Modules, QualifiedTests), + MatchedTests = get_matching_tests(Modules, Tests, QualifiedTests), + {MatchedTests, FilteredModules}. + +%% +%% == get suites specified via 'suites' option == +%% +get_selected_suites(Config, Modules) -> + RawSuites = get_suites(Config), + Suites = [list_to_atom(Suite) || Suite <- string:tokens(RawSuites, ",")], + [M || M <- Suites, lists:member(M, Modules)]. + +get_suites(Config) -> + case rebar_config:get_global(Config, suites, "") of + "" -> + rebar_config:get_global(Config, suite, ""); + Suites -> + Suites + end. + +get_qualified_and_unqualified_tests(Config) -> + RawFunctions = rebar_config:get_global(Config, tests, ""), + FunctionNames = [FunctionName || + FunctionName <- string:tokens(RawFunctions, ",")], + get_qualified_and_unqualified_tests1(FunctionNames, [], []). + +get_qualified_and_unqualified_tests1([], Functions, QualifiedFunctions) -> + {Functions, QualifiedFunctions}; +get_qualified_and_unqualified_tests1([TestName|TestNames], Functions, + QualifiedFunctions) -> + case string:tokens(TestName, ":") of + [TestName] -> + Function = list_to_atom(TestName), + get_qualified_and_unqualified_tests1( + TestNames, [Function|Functions], QualifiedFunctions); + [ModuleName, FunctionName] -> + M = list_to_atom(ModuleName), + F = list_to_atom(FunctionName), + get_qualified_and_unqualified_tests1(TestNames, Functions, + [{M, F}|QualifiedFunctions]); + _ -> + ?ABORT("Unsupported test function specification: ~s~n", [TestName]) + end. + +%% Provide modules which are to be searched for tests. +%% Several scenarios are possible: +%% +%% == randomize suites == +%% + +randomize_suites(Config, Modules) -> + case rebar_config:get_global(Config, random_suite_order, undefined) of + undefined -> + Modules; + "true" -> + Seed = crypto:rand_uniform(1, 65535), + randomize_suites1(Modules, Seed); + String -> + try list_to_integer(String) of + Seed -> + randomize_suites1(Modules, Seed) + catch + error:badarg -> + ?ERROR("Bad random seed provided: ~p~n", [String]), + ?FAIL + end + end. + +randomize_suites1(Modules, Seed) -> + _ = random:seed(35, Seed, 1337), + ?CONSOLE("Randomizing suite order with seed ~b~n", [Seed]), + [X||{_,X} <- lists:sort([{random:uniform(), M} || M <- Modules])]. + +%% +%% == get matching tests == +%% 1) Specific tests have been provided and/or +%% no unqualified tests have been specified and +%% there were some qualified tests, then we can search for +%% functions in specified suites (or in empty set of suites). +%% +%% 2) Neither specific suites nor qualified test names have been +%% provided use ModuleBeamFiles which filters out "*_tests" +%% modules so EUnit won't doubly run them and cover only +%% calculates coverage on production code. However, +%% keep "*_tests" modules that are not automatically +%% included by EUnit. +%% +%% From 'Primitives' in the EUnit User's Guide +%% http://www.erlang.org/doc/apps/eunit/chapter.html +%% "In addition, EUnit will also look for another +%% module whose name is ModuleName plus the suffix +%% _tests, and if it exists, all the tests from that +%% module will also be added. (If ModuleName already +%% contains the suffix _tests, this is not done.) E.g., +%% the specification {module, mymodule} will run all +%% tests in the modules mymodule and mymodule_tests. +%% Typically, the _tests module should only contain +%% test cases that use the public interface of the main +%% module (and no other code)." +get_test_modules(SelectedSuites, Tests, QualifiedTests, ModuleBeamFiles) -> + SuitesProvided = SelectedSuites =/= [], + OnlyQualifiedTestsProvided = QualifiedTests =/= [] andalso Tests =:= [], + if + SuitesProvided orelse OnlyQualifiedTestsProvided -> + SelectedSuites; + true -> + [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || + N <- ModuleBeamFiles] + end. + +get_matching_modules(AllModules, Modules, QualifiedTests) -> + ModuleFilterMapper = + fun({M, _}) -> + case lists:member(M, AllModules) of + true -> {true, M}; + _-> false + end + end, + ModulesFromQualifiedTests = lists:zf(ModuleFilterMapper, QualifiedTests), + lists:usort(Modules ++ ModulesFromQualifiedTests). + +get_matching_tests(Modules, [], []) -> + Modules; +get_matching_tests(Modules, [], QualifiedTests) -> + FilteredQualifiedTests = filter_qualified_tests(Modules, QualifiedTests), + lists:merge(Modules, make_test_primitives(FilteredQualifiedTests)); +get_matching_tests(Modules, Tests, QualifiedTests) -> + AllTests = lists:merge(QualifiedTests, + get_matching_tests1(Modules, Tests, [])), + make_test_primitives(AllTests). + +filter_qualified_tests(Modules, QualifiedTests) -> + TestsFilter = fun({Module, _Function}) -> + lists:all(fun(M) -> M =/= Module end, Modules) end, + lists:filter(TestsFilter, QualifiedTests). + +get_matching_tests1([], _Functions, TestFunctions) -> + TestFunctions; + +get_matching_tests1([Module|TModules], Functions, TestFunctions) -> + %% Get module exports + ModuleStr = atom_to_list(Module), + ModuleExports = get_beam_test_exports(ModuleStr), + %% Get module _tests exports + TestModuleStr = string:concat(ModuleStr, "_tests"), + TestModuleExports = get_beam_test_exports(TestModuleStr), + %% Build tests {M, F} list + Tests = get_matching_tests2(Functions, {Module, ModuleExports}, + {list_to_atom(TestModuleStr), + TestModuleExports}), + get_matching_tests1(TModules, Functions, + lists:merge([TestFunctions, Tests])). + +get_matching_tests2(Functions, {Mod, ModExports}, {TestMod, TestModExports}) -> + %% Look for matching functions into ModExports + ModExportsStr = [atom_to_list(E1) || E1 <- ModExports], + TestModExportsStr = [atom_to_list(E2) || E2 <- TestModExports], + get_matching_exports(Functions, {Mod, ModExportsStr}, + {TestMod, TestModExportsStr}, []). + +get_matching_exports([], _, _, Matched) -> + Matched; +get_matching_exports([Function|TFunctions], {Mod, ModExportsStr}, + {TestMod, TestModExportsStr}, Matched) -> + + FunctionStr = atom_to_list(Function), + %% Get matching Function in module, otherwise look in _tests module + NewMatch = case get_matching_export(FunctionStr, ModExportsStr) of + [] -> + {TestMod, get_matching_export(FunctionStr, + TestModExportsStr)}; + MatchingExport -> + {Mod, MatchingExport} + end, + case NewMatch of + {_, []} -> + get_matching_exports(TFunctions, {Mod, ModExportsStr}, + {TestMod, TestModExportsStr}, Matched); + _ -> + get_matching_exports(TFunctions, {Mod, ModExportsStr}, + {TestMod, TestModExportsStr}, + [NewMatch|Matched]) + end. + +get_matching_export(_FunctionStr, []) -> + []; +get_matching_export(FunctionStr, [ExportStr|TExportsStr]) -> + case string:str(ExportStr, FunctionStr) of + 1 -> + list_to_atom(ExportStr); + _ -> + get_matching_export(FunctionStr, TExportsStr) + end. + +get_beam_test_exports(ModuleStr) -> + FilePath = filename:join(eunit_dir(), + string:concat(ModuleStr, ".beam")), + case filelib:is_regular(FilePath) of + true -> + {beam_file, _, Exports0, _, _, _} = beam_disasm:file(FilePath), + Exports1 = [FunName || {FunName, FunArity, _} <- Exports0, + FunArity =:= 0], + F = fun(FName) -> + FNameStr = atom_to_list(FName), + re:run(FNameStr, "_test(_)?") =/= nomatch + end, + lists:filter(F, Exports1); + _ -> + [] + end. + +make_test_primitives(RawTests) -> + %% Use {test,M,F} and {generator,M,F} if at least R15B02. Otherwise, + %% use eunit_test:function_wrapper/2 fallback. + %% eunit_test:function_wrapper/2 was renamed to eunit_test:mf_wrapper/2 + %% in R15B02; use that as >= R15B02 check. + %% TODO: remove fallback and use only {test,M,F} and {generator,M,F} + %% primitives once at least R15B02 is required. + {module, eunit_test} = code:ensure_loaded(eunit_test), + MakePrimitive = case erlang:function_exported(eunit_test, mf_wrapper, 2) of + true -> fun eunit_primitive/3; + false -> fun pre15b02_eunit_primitive/3 + end, + + ?CONSOLE(" Running test function(s):~n", []), + F = fun({M, F2}, Acc) -> + ?CONSOLE(" ~p:~p/0~n", [M, F2]), + FNameStr = atom_to_list(F2), + NewFunction = + case re:run(FNameStr, "_test_") of + nomatch -> + %% Normal test + MakePrimitive(test, M, F2); + _ -> + %% Generator + MakePrimitive(generator, M, F2) + end, + [NewFunction|Acc] + end, + lists:foldl(F, [], RawTests). + +eunit_primitive(Type, M, F) -> + {Type, M, F}. + +pre15b02_eunit_primitive(test, M, F) -> + eunit_test:function_wrapper(M, F); +pre15b02_eunit_primitive(generator, M, F) -> + {generator, eunit_test:function_wrapper(M, F)}. -perform_eunit(Config, Modules) -> - %% suite defined, so only specify the module that relates to the - %% suite (if any). Suite can be a comma seperated list of modules to run. - Suite = rebar_config:get_global(suite, undefined), +%% +%% == run tests == +%% + +perform_eunit(Config, Tests) -> EunitOpts = get_eunit_opts(Config), %% Move down into ?EUNIT_DIR while we run tests so any generated files @@ -184,22 +484,16 @@ Cwd = rebar_utils:get_cwd(), ok = file:set_cwd(?EUNIT_DIR), - EunitResult = perform_eunit(EunitOpts, Modules, Suite), + EunitResult = (catch eunit:test(Tests, EunitOpts)), %% Return to original working dir ok = file:set_cwd(Cwd), EunitResult. -perform_eunit(EunitOpts, Modules, undefined) -> - (catch eunit:test(Modules, EunitOpts)); -perform_eunit(EunitOpts, _Modules, Suites) -> - (catch eunit:test([list_to_atom(Suite) || - Suite <- string:tokens(Suites, ",")], EunitOpts)). - get_eunit_opts(Config) -> %% Enable verbose in eunit if so requested.. - BaseOpts = case rebar_config:is_verbose() of + BaseOpts = case rebar_log:is_verbose(Config) of true -> [verbose]; false -> @@ -208,260 +502,18 @@ 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. - -perform_cover(Config, BeamFiles, SrcModules) -> - perform_cover(rebar_config:get(Config, cover_enabled, false), - Config, BeamFiles, SrcModules). - -perform_cover(false, _Config, _BeamFiles, _SrcModules) -> - ok; -perform_cover(true, Config, BeamFiles, SrcModules) -> - cover_analyze(Config, BeamFiles, SrcModules). - -cover_analyze(_Config, [], _SrcModules) -> - ok; -cover_analyze(Config, Modules, SrcModules) -> - %% suite can be a comma seperated list of modules to test - Suite = [list_to_atom(S) || - S <- string:tokens(rebar_config:get_global(suite, ""), ",")], - FilteredModules = case Suite of - [] -> Modules; - _ -> [M || M <- Modules, lists:member(M, Suite)] - end, - - %% Generate coverage info for all the cover-compiled modules - Coverage = lists:flatten([cover_analyze_mod(M) || M <- FilteredModules]), - - %% Write index of coverage info - cover_write_index(lists:sort(Coverage), SrcModules), - - %% Write coverage details for each file - lists:foreach(fun({M, _, _}) -> - {ok, _} = cover:analyze_to_file(M, cover_file(M), - [html]) - end, Coverage), - - Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]), - ?CONSOLE("Cover analysis: ~s\n", [Index]), - - %% Print coverage report, if configured - case rebar_config:get(Config, cover_print_enabled, false) of - true -> - cover_print_coverage(lists:sort(Coverage)); - false -> - ok - end. - -cover_close(not_enabled) -> - ok; -cover_close(F) -> - ok = file: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 - %% .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 - end, - - {ok, F} = OkOpen = file:open( - filename:join([?EUNIT_DIR, "cover.log"]), - [write]), - - 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], - case [Module || {_, {ok, Module}} <- Compiled] of - [] -> - %% No modules compiled successfully...fail - ?ERROR("Cover failed to compile any modules; aborting.~n", []), - ?FAIL; - _ -> - %% At least one module compiled successfully - - %% It's not an error for cover compilation to fail partially, - %% but we do want to warn about them - PrintWarning = - fun(Beam, Desc) -> - ?CONSOLE("Cover compilation warning for ~p: ~p", - [Beam, Desc]) - end, - _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled], - OkOpen - end; -cover_init(Config, BeamFiles) -> - cover_init(rebar_config:get(Config, cover_enabled, false), BeamFiles). - -cover_analyze_mod(Module) -> - case cover:analyze(Module, coverage, module) of - {ok, {Module, {Covered, NotCovered}}} -> - %% Modules that include the eunit header get an implicit - %% test/0 fun, which cover considers a runnable line, but - %% eunit:test(TestRepresentation) never calls. Decrement - %% NotCovered in this case. - [align_notcovered_count(Module, Covered, NotCovered, - is_eunitized(Module))]; - {error, Reason} -> - ?ERROR("Cover analyze failed for ~p: ~p ~p\n", - [Module, Reason, code:which(Module)]), - [] - end. - -is_eunitized(Mod) -> - has_eunit_test_fun(Mod) andalso - has_header(Mod, "include/eunit.hrl"). - -has_eunit_test_fun(Mod) -> - [F || {exports, Funs} <- Mod:module_info(), - {F, 0} <- Funs, F =:= test] =/= []. - -has_header(Mod, Header) -> - Mod1 = case code:which(Mod) of - cover_compiled -> - {file, File} = cover:is_compiled(Mod), - File; - non_existing -> Mod; - preloaded -> Mod; - L -> L - end, - {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Mod1, - [abstract_code]), - [F || {attribute, 1, file, {F, 1}} <- AC, - string:str(F, Header) =/= 0] =/= []. - -align_notcovered_count(Module, Covered, NotCovered, false) -> - {Module, Covered, NotCovered}; -align_notcovered_count(Module, Covered, NotCovered, true) -> - {Module, Covered, NotCovered - 1}. - -cover_write_index(Coverage, SrcModules) -> - {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]), - ok = file:write(F, "Coverage Summary\n"), - IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, - {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), - cover_write_index_section(F, "Source", SrcCoverage), - cover_write_index_section(F, "Test", TestCoverage), - ok = file:write(F, ""), - ok = file:close(F). - -cover_write_index_section(_F, _SectionName, []) -> - ok; -cover_write_index_section(F, SectionName, Coverage) -> - %% Calculate total coverage % - {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) -> - {CAcc + C, NAcc + N} - end, {0, 0}, Coverage), - TotalCoverage = percentage(Covered, NotCovered), - - %% Write the report - ok = file:write(F, ?FMT("

    ~s Summary

    \n", [SectionName])), - ok = file:write(F, ?FMT("

    Total: ~s

    \n", [TotalCoverage])), - ok = file:write(F, "\n"), - - FmtLink = - fun(Module, Cov, NotCov) -> - ?FMT("\n", - [Module, Module, percentage(Cov, NotCov)]) - end, - lists:foreach(fun({Module, Cov, NotCov}) -> - ok = file:write(F, FmtLink(Module, Cov, NotCov)) - end, Coverage), - ok = file:write(F, "
    ModuleCoverage %
    ~s~s
    \n"). - -cover_print_coverage(Coverage) -> - {Covered, NotCovered} = lists:foldl(fun({_Mod, C, N}, {CAcc, NAcc}) -> - {CAcc + C, NAcc + N} - end, {0, 0}, Coverage), - TotalCoverage = percentage(Covered, NotCovered), - - %% Determine the longest module name for right-padding - Width = lists:foldl(fun({Mod, _, _}, Acc) -> - case length(atom_to_list(Mod)) of - N when N > Acc -> - N; - _ -> - Acc - end - end, 0, Coverage) * -1, - - %% Print the output the console - ?CONSOLE("~nCode Coverage:~n", []), - lists:foreach(fun({Mod, C, N}) -> - ?CONSOLE("~*s : ~3s~n", - [Width, Mod, percentage(C, N)]) - end, Coverage), - ?CONSOLE("~n~*s : ~s~n", [Width, "Total", TotalCoverage]). - -cover_file(Module) -> - filename:join([?EUNIT_DIR, atom_to_list(Module) ++ ".COVER.html"]). - -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 -> @@ -482,7 +534,8 @@ end, ok = application:unset_env(App, K) end || App <- Apps, App /= rebar, - {K, _V} <- application:get_all_env(App)], + {K, _V} <- application:get_all_env(App), + K =/= included_applications], reconstruct_app_env_vars(Apps), @@ -614,12 +667,11 @@ pause_until_net_kernel_stopped(0) -> exit(net_kernel_stop_failed); pause_until_net_kernel_stopped(N) -> - try - _ = net_kernel:i(), - timer:sleep(100), - pause_until_net_kernel_stopped(N - 1) - catch - error:badarg -> + case node() of + 'nonode@nohost' -> ?DEBUG("Stopped net kernel.\n", []), - ok + ok; + _ -> + timer:sleep(100), + pause_until_net_kernel_stopped(N - 1) end. diff -Nru rebar-2.0.0/src/rebar_file_utils.erl rebar-2.6.0/src/rebar_file_utils.erl --- rebar-2.0.0/src/rebar_file_utils.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_file_utils.erl 2015-06-19 16:14:28.000000000 +0000 @@ -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,13 +40,13 @@ %% @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, _} -> - EscTarget = escape_spaces(Target), + EscTarget = escape_path(Target), {ok, []} = rebar_utils:sh(?FMT("rm -rf ~s", [EscTarget]), - [{use_stdout, false}, return_on_error]), + [{use_stdout, false}, abort_on_error]), ok; {win32, _} -> Filelist = filelib:wildcard(Target), @@ -56,29 +57,31 @@ 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, _} -> - EscSources = [escape_spaces(Src) || Src <- Sources], + EscSources = [escape_path(Src) || Src <- Sources], SourceStr = string:join(EscSources, " "), {ok, []} = rebar_utils:sh(?FMT("cp -R ~s \"~s\"", [SourceStr, Dest]), - [{use_stdout, false}, return_on_error]), + [{use_stdout, false}, abort_on_error]), ok; {win32, _} -> lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources), ok end. --spec mv(Source::string(), Dest::file:filename()) -> ok. +-spec mv(string(), file:filename()) -> 'ok'. mv(Source, Dest) -> case os:type() of {unix, _} -> - EscSource = escape_spaces(Source), - EscDest = escape_spaces(Dest), + EscSource = escape_path(Source), + EscDest = escape_path(Dest), {ok, []} = rebar_utils:sh(?FMT("mv ~s ~s", [EscSource, EscDest]), - [{use_stdout, false}, return_on_error]), + [{use_stdout, false}, abort_on_error]), ok; {win32, _} -> {ok, R} = rebar_utils:sh( @@ -109,6 +112,17 @@ ?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. + %% =================================================================== %% Internal functions %% =================================================================== @@ -176,5 +190,5 @@ end, filelib:wildcard(Source)), ok. -escape_spaces(Str) -> - re:replace(Str, " ", "\\\\ ", [global, {return, list}]). +escape_path(Str) -> + re:replace(Str, "([ ()?])", "\\\\&", [global, {return, list}]). diff -Nru rebar-2.0.0/src/rebar_getopt.erl rebar-2.6.0/src/rebar_getopt.erl --- rebar-2.0.0/src/rebar_getopt.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_getopt.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,914 @@ +%%%------------------------------------------------------------------- +%%% @author Juan Jose Comellas +%%% @copyright (C) 2009 Juan Jose Comellas +%%% @doc Parses command line options with a format similar to that of GNU getopt. +%%% @end +%%% +%%% This source file is subject to the New BSD License. You should have received +%%% a copy of the New BSD license with this software. If not, it can be +%%% retrieved from: http://www.opensource.org/licenses/bsd-license.php +%%%------------------------------------------------------------------- +-module(rebar_getopt). +-author('juanjo@comellas.org'). + +-export([parse/2, check/2, parse_and_check/2, format_error/2, + usage/2, usage/3, usage/4, tokenize/1]). +-export([usage_cmd_line/2]). + +-define(LINE_LENGTH, 75). +-define(MIN_USAGE_COMMAND_LINE_OPTION_LENGTH, 25). + +%% Position of each field in the option specification tuple. +-define(OPT_NAME, 1). +-define(OPT_SHORT, 2). +-define(OPT_LONG, 3). +-define(OPT_ARG, 4). +-define(OPT_HELP, 5). + +-define(IS_OPT_SPEC(Opt), (tuple_size(Opt) =:= ?OPT_HELP)). +-define(IS_WHITESPACE(Char), ((Char) =:= $\s orelse (Char) =:= $\t orelse + (Char) =:= $\n orelse (Char) =:= $\r)). + +%% Atom indicating the data type that an argument can be converted to. +-type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'. +%% Data type that an argument can be converted to. +-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string(). +%% Argument specification. +-type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined. +%% Option type and optional default argument. +-type simple_option() :: atom(). +-type compound_option() :: {atom(), arg_value()}. +-type option() :: simple_option() | compound_option(). +%% Command line option specification. +-type option_spec() :: { + Name :: atom(), + Short :: char() | undefined, + Long :: string() | undefined, + ArgSpec :: arg_spec(), + Help :: string() | undefined + }. +%% Output streams +-type output_stream() :: 'standard_io' | 'standard_error'. + +%% For internal use +-type usage_line() :: {OptionText :: string(), HelpText :: string()}. +-type usage_line_with_length() :: {OptionLength :: non_neg_integer(), OptionText :: string(), HelpText :: string()}. + + +-export_type([arg_type/0, arg_value/0, arg_spec/0, simple_option/0, compound_option/0, option/0, option_spec/0]). + + +%% @doc Parse the command line options and arguments returning a list of tuples +%% and/or atoms using the Erlang convention for sending options to a +%% function. Additionally perform check if all required options (the ones +%% without default values) are present. The function is a combination of +%% two calls: parse/2 and check/2. +-spec parse_and_check([option_spec()], string() | [string()]) -> + {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: term()}}. +parse_and_check(OptSpecList, CmdLine) when is_list(OptSpecList), is_list(CmdLine) -> + case parse(OptSpecList, CmdLine) of + {ok, {Opts, _}} = Result -> + case check(OptSpecList, Opts) of + ok -> Result; + Error -> Error + end; + Error -> + Error + end. + +%% @doc Check the parsed command line arguments returning ok if all required +%% options (i.e. that don't have defaults) are present, and returning +%% error otherwise. +-spec check([option_spec()], [option()]) -> + ok | {error, {Reason :: atom(), Option :: atom()}}. +check(OptSpecList, ParsedOpts) when is_list(OptSpecList), is_list(ParsedOpts) -> + try + RequiredOpts = [Name || {Name, _, _, Arg, _} <- OptSpecList, + not is_tuple(Arg) andalso Arg =/= undefined], + lists:foreach(fun (Option) -> + case proplists:is_defined(Option, ParsedOpts) of + true -> + ok; + false -> + throw({error, {missing_required_option, Option}}) + end + end, RequiredOpts) + catch + _:Error -> + Error + end. + + +%% @doc Parse the command line options and arguments returning a list of tuples +%% and/or atoms using the Erlang convention for sending options to a +%% function. +-spec parse([option_spec()], string() | [string()]) -> + {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: term()}}. +parse(OptSpecList, CmdLine) when is_list(CmdLine) -> + try + Args = if + is_integer(hd(CmdLine)) -> tokenize(CmdLine); + true -> CmdLine + end, + parse(OptSpecList, [], [], 0, Args) + catch + throw: {error, {_Reason, _Data}} = Error -> + Error + end. + + +-spec parse([option_spec()], [option()], [string()], integer(), [string()]) -> + {ok, {[option()], [string()]}}. +%% Process the option terminator. +parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) -> + %% Any argument present after the terminator is not considered an option. + {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}}; +%% Process long options. +parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) -> + parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); +%% Process short options. +parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) -> + parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); +%% Process non-option arguments. +parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) -> + case find_non_option_arg(OptSpecList, ArgPos) of + {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) -> + parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos + 1, Tail); + false -> + parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail) + end; +parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) -> + %% Once we have completed gathering the options we add the ones that were + %% not present but had default arguments in the specification. + {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}. + + +%% @doc Format the error code returned by prior call to parse/2 or check/2. +-spec format_error([option_spec()], {error, {Reason :: atom(), Data :: term()}} | + {Reason :: term(), Data :: term()}) -> string(). +format_error(OptSpecList, {error, Reason}) -> + format_error(OptSpecList, Reason); +format_error(OptSpecList, {missing_required_option, Name}) -> + {_Name, Short, Long, _Type, _Help} = lists:keyfind(Name, 1, OptSpecList), + lists:flatten(["missing required option: -", [Short], " (", to_string(Long), ")"]); +format_error(_OptSpecList, {invalid_option, OptStr}) -> + lists:flatten(["invalid option: ", to_string(OptStr)]); +format_error(_OptSpecList, {invalid_option_arg, {Name, Arg}}) -> + lists:flatten(["option \'", to_string(Name) ++ "\' has invalid argument: ", to_string(Arg)]); +format_error(_OptSpecList, {invalid_option_arg, OptStr}) -> + lists:flatten(["invalid option argument: ", to_string(OptStr)]); +format_error(_OptSpecList, {Reason, Data}) -> + lists:flatten([to_string(Reason), " ", to_string(Data)]). + + +%% @doc Parse a long option, add it to the option accumulator and continue +%% parsing the rest of the arguments recursively. +%% A long option can have the following syntax: +%% --foo Single option 'foo', no argument +%% --foo=bar Single option 'foo', argument "bar" +%% --foo bar Single option 'foo', argument "bar" +-spec parse_long_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> + {ok, {[option()], [string()]}}. +parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> + case split_assigned_arg(OptArg) of + {Long, Arg} -> + %% Get option that has its argument within the same string + %% separated by an equal ('=') character (e.g. "--port=1000"). + parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg); + + Long -> + case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of + {Name, _Short, Long, undefined, _Help} -> + parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args); + + {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec -> + %% The option argument string is empty, but the option requires + %% an argument, so we look into the next string in the list. + %% e.g ["--port", "1000"] + parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); + false -> + throw({error, {invalid_option, OptStr}}) + end + end. + + +%% @doc Parse an option where the argument is 'assigned' in the same string using +%% the '=' character, add it to the option accumulator and continue parsing the +%% rest of the arguments recursively. This syntax is only valid for long options. +-spec parse_long_option_assigned_arg([option_spec()], [option()], [string()], integer(), + [string()], string(), string(), string()) -> + {ok, {[option()], [string()]}}. +parse_long_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) -> + case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of + {_Name, _Short, Long, ArgSpec, _Help} = OptSpec -> + case ArgSpec of + undefined -> + throw({error, {invalid_option_arg, OptStr}}); + _ -> + parse(OptSpecList, add_option_with_assigned_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args) + end; + false -> + throw({error, {invalid_option, OptStr}}) + end. + + +%% @doc Split an option string that may contain an option with its argument +%% separated by an equal ('=') character (e.g. "port=1000"). +-spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string(). +split_assigned_arg(OptStr) -> + split_assigned_arg(OptStr, OptStr, []). + +split_assigned_arg(_OptStr, "=" ++ Tail, Acc) -> + {lists:reverse(Acc), Tail}; +split_assigned_arg(OptStr, [Char | Tail], Acc) -> + split_assigned_arg(OptStr, Tail, [Char | Acc]); +split_assigned_arg(OptStr, [], _Acc) -> + OptStr. + + +%% @doc Retrieve the argument for an option from the next string in the list of +%% command-line parameters or set the value of the argument from the argument +%% specification (for boolean and integer arguments), if possible. +parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) -> + ArgSpecType = arg_spec_type(ArgSpec), + case Args =:= [] orelse is_implicit_arg(ArgSpecType, hd(Args)) of + true -> + parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); + false -> + [Arg | Tail] = Args, + try + parse(OptSpecList, [{Name, to_type(ArgSpecType, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) + catch + error:_ -> + throw({error, {invalid_option_arg, {Name, Arg}}}) + end + end. + + +%% @doc Parse a short option, add it to the option accumulator and continue +%% parsing the rest of the arguments recursively. +%% A short option can have the following syntax: +%% -a Single option 'a', no argument or implicit boolean argument +%% -a foo Single option 'a', argument "foo" +%% -afoo Single option 'a', argument "foo" +%% -abc Multiple options: 'a'; 'b'; 'c' +%% -bcafoo Multiple options: 'b'; 'c'; 'a' with argument "foo" +%% -aaa Multiple repetitions of option 'a' (only valid for options with integer arguments) +-spec parse_short_option([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> + {ok, {[option()], [string()]}}. +parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> + parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg). + +parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptPos, [Short | Arg]) -> + case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of + {Name, Short, _Long, undefined, _Help} -> + parse_short_option(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, first, Arg); + + {_Name, Short, _Long, ArgSpec, _Help} = OptSpec -> + %% The option has a specification, so it requires an argument. + case Arg of + [] -> + %% The option argument string is empty, but the option requires + %% an argument, so we look into the next string in the list. + parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec, OptPos); + + _ -> + case is_valid_arg(ArgSpec, Arg) of + true -> + parse(OptSpecList, add_option_with_arg(OptSpec, Arg, OptAcc), ArgAcc, ArgPos, Args); + _ -> + NewOptAcc = case OptPos of + first -> add_option_with_implicit_arg(OptSpec, OptAcc); + _ -> add_option_with_implicit_incrementable_arg(OptSpec, OptAcc) + end, + parse_short_option(OptSpecList, NewOptAcc, ArgAcc, ArgPos, Args, OptStr, next, Arg) + end + end; + + false -> + throw({error, {invalid_option, OptStr}}) + end; +parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, _OptPos, []) -> + parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args). + + +%% @doc Retrieve the argument for an option from the next string in the list of +%% command-line parameters or set the value of the argument from the argument +%% specification (for boolean and integer arguments), if possible. +parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec, OptPos) -> + case Args =:= [] orelse is_implicit_arg(ArgSpec, hd(Args)) of + true when OptPos =:= first -> + parse(OptSpecList, add_option_with_implicit_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); + true -> + parse(OptSpecList, add_option_with_implicit_incrementable_arg(OptSpec, OptAcc), ArgAcc, ArgPos, Args); + false -> + [Arg | Tail] = Args, + try + parse(OptSpecList, [{Name, to_type(ArgSpec, Arg)} | OptAcc], ArgAcc, ArgPos, Tail) + catch + error:_ -> + throw({error, {invalid_option_arg, {Name, Arg}}}) + end + end. + + +%% @doc Find the option for the discrete argument in position specified in the +%% Pos argument. +-spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false. +find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} = OptSpec | _Tail], 0) -> + {value, OptSpec}; +find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} | Tail], Pos) -> + find_non_option_arg(Tail, Pos - 1); +find_non_option_arg([_Head | Tail], Pos) -> + find_non_option_arg(Tail, Pos); +find_non_option_arg([], _Pos) -> + false. + + +%% @doc Append options that were not present in the command line arguments with +%% their default arguments. +-spec append_default_options([option_spec()], [option()]) -> [option()]. +append_default_options([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) -> + append_default_options(Tail, + case lists:keymember(Name, 1, OptAcc) of + false -> + [{Name, DefaultArg} | OptAcc]; + _ -> + OptAcc + end); +%% For options with no default argument. +append_default_options([_Head | Tail], OptAcc) -> + append_default_options(Tail, OptAcc); +append_default_options([], OptAcc) -> + OptAcc. + + +%% @doc Add an option with argument converting it to the data type indicated by the +%% argument specification. +-spec add_option_with_arg(option_spec(), string(), [option()]) -> [option()]. +add_option_with_arg({Name, _Short, _Long, ArgSpec, _Help} = OptSpec, Arg, OptAcc) -> + case is_valid_arg(ArgSpec, Arg) of + true -> + try + [{Name, to_type(ArgSpec, Arg)} | OptAcc] + catch + error:_ -> + throw({error, {invalid_option_arg, {Name, Arg}}}) + end; + false -> + add_option_with_implicit_arg(OptSpec, OptAcc) + end. + + +%% @doc Add an option with argument that was part of an assignment expression +%% (e.g. "--verbose=3") converting it to the data type indicated by the +%% argument specification. +-spec add_option_with_assigned_arg(option_spec(), string(), [option()]) -> [option()]. +add_option_with_assigned_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg, OptAcc) -> + try + [{Name, to_type(ArgSpec, Arg)} | OptAcc] + catch + error:_ -> + throw({error, {invalid_option_arg, {Name, Arg}}}) + end. + + +%% @doc Add an option that required an argument but did not have one. Some data +%% types (boolean, integer) allow implicit or assumed arguments. +-spec add_option_with_implicit_arg(option_spec(), [option()]) -> [option()]. +add_option_with_implicit_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> + case arg_spec_type(ArgSpec) of + boolean -> + %% Special case for boolean arguments: if there is no argument we + %% set the value to 'true'. + [{Name, true} | OptAcc]; + integer -> + %% Special case for integer arguments: if the option had not been set + %% before we set the value to 1. This is needed to support options like + %% "-v" to return something like {verbose, 1}. + [{Name, 1} | OptAcc]; + _ -> + throw({error, {missing_option_arg, Name}}) + end. + + +%% @doc Add an option with an implicit or assumed argument. +-spec add_option_with_implicit_incrementable_arg(option_spec() | arg_spec(), [option()]) -> [option()]. +add_option_with_implicit_incrementable_arg({Name, _Short, _Long, ArgSpec, _Help}, OptAcc) -> + case arg_spec_type(ArgSpec) of + boolean -> + %% Special case for boolean arguments: if there is no argument we + %% set the value to 'true'. + [{Name, true} | OptAcc]; + integer -> + %% Special case for integer arguments: if the option had not been set + %% before we set the value to 1; if not we increment the previous value + %% the option had. This is needed to support options like "-vvv" to + %% return something like {verbose, 3}. + case OptAcc of + [{Name, Count} | Tail] -> + [{Name, Count + 1} | Tail]; + _ -> + [{Name, 1} | OptAcc] + end; + _ -> + throw({error, {missing_option_arg, Name}}) + end. + + +%% @doc Retrieve the data type form an argument specification. +-spec arg_spec_type(arg_spec()) -> arg_type() | undefined. +arg_spec_type({Type, _DefaultArg}) -> + Type; +arg_spec_type(Type) when is_atom(Type) -> + Type. + + +%% @doc Convert an argument string to its corresponding data type. +-spec to_type(arg_spec() | arg_type(), string()) -> arg_value(). +to_type({Type, _DefaultArg}, Arg) -> + to_type(Type, Arg); +to_type(binary, Arg) -> + list_to_binary(Arg); +to_type(atom, Arg) -> + list_to_atom(Arg); +to_type(integer, Arg) -> + list_to_integer(Arg); +to_type(float, Arg) -> + list_to_float(Arg); +to_type(boolean, Arg) -> + LowerArg = string:to_lower(Arg), + case is_arg_true(LowerArg) of + true -> + true; + _ -> + case is_arg_false(LowerArg) of + true -> + false; + false -> + erlang:error(badarg) + end + end; +to_type(_Type, Arg) -> + Arg. + + +-spec is_arg_true(string()) -> boolean(). +is_arg_true(Arg) -> + (Arg =:= "true") orelse (Arg =:= "t") orelse + (Arg =:= "yes") orelse (Arg =:= "y") orelse + (Arg =:= "on") orelse (Arg =:= "enabled") orelse + (Arg =:= "1"). + + +-spec is_arg_false(string()) -> boolean(). +is_arg_false(Arg) -> + (Arg =:= "false") orelse (Arg =:= "f") orelse + (Arg =:= "no") orelse (Arg =:= "n") orelse + (Arg =:= "off") orelse (Arg =:= "disabled") orelse + (Arg =:= "0"). + + +-spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean(). +is_valid_arg({Type, _DefaultArg}, Arg) -> + is_valid_arg(Type, Arg); +is_valid_arg(boolean, Arg) -> + is_boolean_arg(Arg); +is_valid_arg(integer, Arg) -> + is_non_neg_integer_arg(Arg); +is_valid_arg(float, Arg) -> + is_non_neg_float_arg(Arg); +is_valid_arg(_Type, _Arg) -> + true. + + +-spec is_implicit_arg(arg_spec(), nonempty_string()) -> boolean(). +is_implicit_arg({Type, _DefaultArg}, Arg) -> + is_implicit_arg(Type, Arg); +is_implicit_arg(boolean, Arg) -> + not is_boolean_arg(Arg); +is_implicit_arg(integer, Arg) -> + not is_integer_arg(Arg); +is_implicit_arg(_Type, _Arg) -> + false. + + +-spec is_boolean_arg(string()) -> boolean(). +is_boolean_arg(Arg) -> + LowerArg = string:to_lower(Arg), + is_arg_true(LowerArg) orelse is_arg_false(LowerArg). + + +-spec is_integer_arg(string()) -> boolean(). +is_integer_arg("-" ++ Tail) -> + is_non_neg_integer_arg(Tail); +is_integer_arg(Arg) -> + is_non_neg_integer_arg(Arg). + + +-spec is_non_neg_integer_arg(string()) -> boolean(). +is_non_neg_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 -> + is_non_neg_integer_arg(Tail); +is_non_neg_integer_arg([_Head | _Tail]) -> + false; +is_non_neg_integer_arg([]) -> + true. + + +-spec is_non_neg_float_arg(string()) -> boolean(). +is_non_neg_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. -> + is_non_neg_float_arg(Tail); +is_non_neg_float_arg([_Head | _Tail]) -> + false; +is_non_neg_float_arg([]) -> + true. + + +%% @doc Show a message on standard_error indicating the command line options and +%% arguments that are supported by the program. +-spec usage([option_spec()], string()) -> ok. +usage(OptSpecList, ProgramName) -> + usage(OptSpecList, ProgramName, standard_error). + + +%% @doc Show a message on standard_error or standard_io indicating the command line options and +%% arguments that are supported by the program. +-spec usage([option_spec()], string(), output_stream() | string()) -> ok. +usage(OptSpecList, ProgramName, OutputStream) when is_atom(OutputStream) -> + io:format(OutputStream, "~s~n~n~s~n", + [usage_cmd_line(ProgramName, OptSpecList), usage_options(OptSpecList)]); +%% @doc Show a message on standard_error indicating the command line options and +%% arguments that are supported by the program. The CmdLineTail argument +%% is a string that is added to the end of the usage command line. +usage(OptSpecList, ProgramName, CmdLineTail) -> + usage(OptSpecList, ProgramName, CmdLineTail, standard_error). + + +%% @doc Show a message on standard_error or standard_io indicating the command line options and +%% arguments that are supported by the program. The CmdLineTail argument +%% is a string that is added to the end of the usage command line. +-spec usage([option_spec()], ProgramName :: string(), CmdLineTail :: string(), output_stream() | [{string(), string()}]) -> ok. +usage(OptSpecList, ProgramName, CmdLineTail, OutputStream) when is_atom(OutputStream) -> + io:format(OutputStream, "~s~n~n~s~n", + [usage_cmd_line(ProgramName, OptSpecList, CmdLineTail), usage_options(OptSpecList)]); +%% @doc Show a message on standard_error indicating the command line options and +%% arguments that are supported by the program. The CmdLineTail and OptionsTail +%% arguments are a string that is added to the end of the usage command line +%% and a list of tuples that are added to the end of the options' help lines. +usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) -> + usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, standard_error). + + +%% @doc Show a message on standard_error or standard_io indicating the command line options and +%% arguments that are supported by the program. The CmdLineTail and OptionsTail +%% arguments are a string that is added to the end of the usage command line +%% and a list of tuples that are added to the end of the options' help lines. +-spec usage([option_spec()], ProgramName :: string(), CmdLineTail :: string(), + [{OptionName :: string(), Help :: string()}], output_stream()) -> ok. +usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail, OutputStream) -> + io:format(OutputStream, "~s~n~n~s~n", + [usage_cmd_line(ProgramName, OptSpecList, CmdLineTail), usage_options(OptSpecList, OptionsTail)]). + + +-spec usage_cmd_line(ProgramName :: string(), [option_spec()]) -> iolist(). +usage_cmd_line(ProgramName, OptSpecList) -> + usage_cmd_line(ProgramName, OptSpecList, ""). + +-spec usage_cmd_line(ProgramName :: string(), [option_spec()], CmdLineTail :: string()) -> iolist(). +usage_cmd_line(ProgramName, OptSpecList, CmdLineTail) -> + Prefix = "Usage: " ++ ProgramName, + PrefixLength = length(Prefix), + LineLength = line_length(), + %% Only align the command line options after the program name when there is + %% enough room to do so (i.e. at least 25 characters). If not, show the + %% command line options below the program name with a 2-character indentation. + if + (LineLength - PrefixLength) > ?MIN_USAGE_COMMAND_LINE_OPTION_LENGTH -> + Indentation = lists:duplicate(PrefixLength, $\s), + [FirstOptLine | OptLines] = usage_cmd_line_options(LineLength - PrefixLength, OptSpecList, CmdLineTail), + IndentedOptLines = [[Indentation | OptLine] || OptLine <- OptLines], + [Prefix, FirstOptLine | IndentedOptLines]; + true -> + IndentedOptLines = [[" " | OptLine] || OptLine <- usage_cmd_line_options(LineLength, OptSpecList, CmdLineTail)], + [Prefix, $\n, IndentedOptLines] + end. + + +%% @doc Return a list of the lines corresponding to the usage command line +%% already wrapped according to the maximum MaxLineLength. +-spec usage_cmd_line_options(MaxLineLength :: non_neg_integer(), [option_spec()], CmdLineTail :: string()) -> iolist(). +usage_cmd_line_options(MaxLineLength, OptSpecList, CmdLineTail) -> + usage_cmd_line_options(MaxLineLength, OptSpecList ++ string:tokens(CmdLineTail, " "), [], 0, []). + +usage_cmd_line_options(MaxLineLength, [OptSpec | Tail], LineAcc, LineAccLength, Acc) -> + Option = [$\s | lists:flatten(usage_cmd_line_option(OptSpec))], + OptionLength = length(Option), + %% We accumulate the options in LineAcc until its length is over the + %% maximum allowed line length. When that happens, we append the line in + %% LineAcc to the list with all the lines in the command line (Acc). + NewLineAccLength = LineAccLength + OptionLength, + if + NewLineAccLength < MaxLineLength -> + usage_cmd_line_options(MaxLineLength, Tail, [Option | LineAcc], NewLineAccLength, Acc); + true -> + usage_cmd_line_options(MaxLineLength, Tail, [Option], OptionLength + 1, + [lists:reverse([$\n | LineAcc]) | Acc]) + end; +usage_cmd_line_options(MaxLineLength, [], [_ | _] = LineAcc, _LineAccLength, Acc) -> + %% If there was a non-empty line in LineAcc when there are no more options + %% to process, we add it to the list of lines to return. + usage_cmd_line_options(MaxLineLength, [], [], 0, [lists:reverse(LineAcc) | Acc]); +usage_cmd_line_options(_MaxLineLength, [], [], _LineAccLength, Acc) -> + lists:reverse(Acc). + + +-spec usage_cmd_line_option(option_spec()) -> string(). +usage_cmd_line_option({_Name, Short, _Long, undefined, _Help}) when Short =/= undefined -> + %% For options with short form and no argument. + [$[, $-, Short, $]]; +usage_cmd_line_option({_Name, _Short, Long, undefined, _Help}) when Long =/= undefined -> + %% For options with only long form and no argument. + [$[, $-, $-, Long, $]]; +usage_cmd_line_option({_Name, _Short, _Long, undefined, _Help}) -> + []; +usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_atom(ArgSpec) -> + %% For options with no default argument. + if + %% For options with short form and argument. + Short =/= undefined -> [$[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]]; + %% For options with only long form and argument. + Long =/= undefined -> [$[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]]; + %% For options with neither short nor long form and argument. + true -> [$[, $<, atom_to_list(Name), $>, $]] + end; +usage_cmd_line_option({Name, Short, Long, ArgSpec, _Help}) when is_tuple(ArgSpec) -> + %% For options with default argument. + if + %% For options with short form and default argument. + Short =/= undefined -> [$[, $-, Short, $\s, $[, $<, atom_to_list(Name), $>, $], $]]; + %% For options with only long form and default argument. + Long =/= undefined -> [$[, $-, $-, Long, $\s, $[, $<, atom_to_list(Name), $>, $], $]]; + %% For options with neither short nor long form and default argument. + true -> [$[, $<, atom_to_list(Name), $>, $]] + end; +usage_cmd_line_option(Option) when is_list(Option) -> + %% For custom options that are added to the command line. + Option. + + +%% @doc Return a list of help messages to print for each of the options and arguments. +-spec usage_options([option_spec()]) -> [string()]. +usage_options(OptSpecList) -> + usage_options(OptSpecList, []). + + +%% @doc Return a list of usage lines to print for each of the options and arguments. +-spec usage_options([option_spec()], [{OptionName :: string(), Help :: string()}]) -> [string()]. +usage_options(OptSpecList, CustomHelp) -> + %% Add the usage lines corresponding to the option specifications. + {MaxOptionLength0, UsageLines0} = add_option_spec_help_lines(OptSpecList, 0, []), + %% Add the custom usage lines. + {MaxOptionLength, UsageLines} = add_custom_help_lines(CustomHelp, MaxOptionLength0, UsageLines0), + MaxLineLength = line_length(), + lists:reverse([format_usage_line(MaxOptionLength + 1, MaxLineLength, UsageLine) || UsageLine <- UsageLines]). + + +-spec add_option_spec_help_lines([option_spec()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) -> + {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}. +add_option_spec_help_lines([OptSpec | Tail], PrevMaxOptionLength, Acc) -> + OptionText = usage_option_text(OptSpec), + HelpText = usage_help_text(OptSpec), + {MaxOptionLength, ColsWithLength} = get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength), + add_option_spec_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]); +add_option_spec_help_lines([], MaxOptionLength, Acc) -> + {MaxOptionLength, Acc}. + + +-spec add_custom_help_lines([usage_line()], PrevMaxOptionLength :: non_neg_integer(), [usage_line_with_length()]) -> + {MaxOptionLength :: non_neg_integer(), [usage_line_with_length()]}. +add_custom_help_lines([CustomCols | Tail], PrevMaxOptionLength, Acc) -> + {MaxOptionLength, ColsWithLength} = get_max_option_length(CustomCols, PrevMaxOptionLength), + add_custom_help_lines(Tail, MaxOptionLength, [ColsWithLength | Acc]); +add_custom_help_lines([], MaxOptionLength, Acc) -> + {MaxOptionLength, Acc}. + + +-spec usage_option_text(option_spec()) -> string(). +usage_option_text({Name, undefined, undefined, _ArgSpec, _Help}) -> + %% Neither short nor long form (non-option argument). + "<" ++ atom_to_list(Name) ++ ">"; +usage_option_text({_Name, Short, undefined, _ArgSpec, _Help}) -> + %% Only short form. + [$-, Short]; +usage_option_text({_Name, undefined, Long, _ArgSpec, _Help}) -> + %% Only long form. + [$-, $- | Long]; +usage_option_text({_Name, Short, Long, _ArgSpec, _Help}) -> + %% Both short and long form. + [$-, Short, $,, $\s, $-, $- | Long]. + + +-spec usage_help_text(option_spec()) -> string(). +usage_help_text({_Name, _Short, _Long, {_ArgType, ArgValue}, [_ | _] = Help}) -> + Help ++ " [default: " ++ default_arg_value_to_string(ArgValue) ++ "]"; +usage_help_text({_Name, _Short, _Long, _ArgSpec, Help}) -> + Help. + + +%% @doc Calculate the maximum width of the column that shows the option's short +%% and long form. +-spec get_max_option_length(usage_line(), PrevMaxOptionLength :: non_neg_integer()) -> + {MaxOptionLength :: non_neg_integer(), usage_line_with_length()}. +get_max_option_length({OptionText, HelpText}, PrevMaxOptionLength) -> + OptionLength = length(OptionText), + {erlang:max(OptionLength, PrevMaxOptionLength), {OptionLength, OptionText, HelpText}}. + + +%% @doc Format the usage line that is shown for the options' usage. Each usage +%% line has 2 columns. The first column shows the options in their short +%% and long form. The second column shows the wrapped (if necessary) help +%% text lines associated with each option. e.g.: +%% +%% -h, --host Database server host name or IP address; this is the +%% hostname of the server where the database is running +%% [default: localhost] +%% -p, --port Database server port [default: 1000] +%% +-spec format_usage_line(MaxOptionLength :: non_neg_integer(), MaxLineLength :: non_neg_integer(), + usage_line_with_length()) -> iolist(). +format_usage_line(MaxOptionLength, MaxLineLength, {OptionLength, OptionText, [_ | _] = HelpText}) + when MaxOptionLength < (MaxLineLength div 2) -> + %% If the width of the column where the options are shown is smaller than + %% half the width of a console line then we show the help text line aligned + %% next to its corresponding option, with a separation of at least 2 + %% characters. + [Head | Tail] = wrap_text_line(MaxLineLength - MaxOptionLength - 3, HelpText), + FirstLineIndentation = lists:duplicate(MaxOptionLength - OptionLength + 1, $\s), + Indentation = [$\n | lists:duplicate(MaxOptionLength + 3, $\s)], + [" ", OptionText, FirstLineIndentation, Head, + [[Indentation, Line] || Line <- Tail], $\n]; +format_usage_line(_MaxOptionLength, MaxLineLength, {_OptionLength, OptionText, [_ | _] = HelpText}) -> + %% If the width of the first column is bigger than the width of a console + %% line, we show the help text on the next line with an indentation of 6 + %% characters. + HelpLines = wrap_text_line(MaxLineLength - 6, HelpText), + [" ", OptionText, [["\n ", Line] || Line <- HelpLines], $\n]; +format_usage_line(_MaxOptionLength, _MaxLineLength, {_OptionLength, OptionText, _HelpText}) -> + [" ", OptionText, $\n]. + + +%% @doc Wrap a text line converting it into several text lines so that the +%% length of each one of them is never over Length characters. +-spec wrap_text_line(Length :: non_neg_integer(), Text :: string()) -> [string()]. +wrap_text_line(Length, Text) -> + wrap_text_line(Length, Text, [], 0, []). + +wrap_text_line(Length, [Char | Tail], Acc, Count, CurrentLineAcc) when Count < Length -> + wrap_text_line(Length, Tail, Acc, Count + 1, [Char | CurrentLineAcc]); +wrap_text_line(Length, [_ | _] = Help, Acc, Count, CurrentLineAcc) -> + %% Look for the first whitespace character in the current (reversed) line + %% buffer to get a wrapped line. If there is no whitespace just cut the + %% line at the position corresponding to the maximum length. + {NextLineAcc, WrappedLine} = case string:cspan(CurrentLineAcc, " \t") of + WhitespacePos when WhitespacePos < Count -> + lists:split(WhitespacePos, CurrentLineAcc); + _ -> + {[], CurrentLineAcc} + end, + wrap_text_line(Length, Help, [lists:reverse(WrappedLine) | Acc], length(NextLineAcc), NextLineAcc); +wrap_text_line(_Length, [], Acc, _Count, [_ | _] = CurrentLineAcc) -> + %% If there was a non-empty line when we reached the buffer, add it to the accumulator + lists:reverse([lists:reverse(CurrentLineAcc) | Acc]); +wrap_text_line(_Length, [], Acc, _Count, _CurrentLineAcc) -> + lists:reverse(Acc). + + +default_arg_value_to_string(Value) when is_atom(Value) -> + atom_to_list(Value); +default_arg_value_to_string(Value) when is_binary(Value) -> + binary_to_list(Value); +default_arg_value_to_string(Value) when is_integer(Value) -> + integer_to_list(Value); +default_arg_value_to_string(Value) when is_float(Value) -> + lists:flatten(io_lib:format("~w", [Value])); +default_arg_value_to_string(Value) -> + Value. + + +%% @doc Tokenize a command line string with support for single and double +%% quoted arguments (needed for arguments that have embedded whitespace). +%% The function also supports the expansion of environment variables in +%% both the Unix (${VAR}; $VAR) and Windows (%VAR%) formats. It does NOT +%% support wildcard expansion of paths. +-spec tokenize(CmdLine :: string()) -> [nonempty_string()]. +tokenize(CmdLine) -> + tokenize(CmdLine, [], []). + +-spec tokenize(CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()]. +tokenize([Sep | Tail], Acc, ArgAcc) when ?IS_WHITESPACE(Sep) -> + NewAcc = case ArgAcc of + [_ | _] -> + %% Found separator: add to the list of arguments. + [lists:reverse(ArgAcc) | Acc]; + [] -> + %% Found separator with no accumulated argument; discard it. + Acc + end, + tokenize(Tail, NewAcc, []); +tokenize([QuotationMark | Tail], Acc, ArgAcc) when QuotationMark =:= $"; QuotationMark =:= $' -> + %% Quoted argument (might contain spaces, tabs, etc.) + tokenize_quoted_arg(QuotationMark, Tail, Acc, ArgAcc); +tokenize([Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% -> + %% Unix and Windows environment variable expansion: ${VAR}; $VAR; %VAR% + {NewCmdLine, Var} = expand_env_var(CmdLine), + tokenize(NewCmdLine, Acc, lists:reverse(Var, ArgAcc)); +tokenize([$\\, Char | Tail], Acc, ArgAcc) -> + %% Escaped char. + tokenize(Tail, Acc, [Char | ArgAcc]); +tokenize([Char | Tail], Acc, ArgAcc) -> + tokenize(Tail, Acc, [Char | ArgAcc]); +tokenize([], Acc, []) -> + lists:reverse(Acc); +tokenize([], Acc, ArgAcc) -> + lists:reverse([lists:reverse(ArgAcc) | Acc]). + +-spec tokenize_quoted_arg(QuotationMark :: char(), CmdLine :: string(), Acc :: [string()], ArgAcc :: string()) -> [string()]. +tokenize_quoted_arg(QuotationMark, [QuotationMark | Tail], Acc, ArgAcc) -> + %% End of quoted argument + tokenize(Tail, Acc, ArgAcc); +tokenize_quoted_arg(QuotationMark, [$\\, Char | Tail], Acc, ArgAcc) -> + %% Escaped char. + tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]); +tokenize_quoted_arg($" = QuotationMark, [Char | _Tail] = CmdLine, Acc, ArgAcc) when Char =:= $$; Char =:= $% -> + %% Unix and Windows environment variable expansion (only for double-quoted arguments): ${VAR}; $VAR; %VAR% + {NewCmdLine, Var} = expand_env_var(CmdLine), + tokenize_quoted_arg(QuotationMark, NewCmdLine, Acc, lists:reverse(Var, ArgAcc)); +tokenize_quoted_arg(QuotationMark, [Char | Tail], Acc, ArgAcc) -> + tokenize_quoted_arg(QuotationMark, Tail, Acc, [Char | ArgAcc]); +tokenize_quoted_arg(_QuotationMark, CmdLine, Acc, ArgAcc) -> + tokenize(CmdLine, Acc, ArgAcc). + + +-spec expand_env_var(CmdLine :: nonempty_string()) -> {string(), string()}. +expand_env_var(CmdLine) -> + case CmdLine of + "${" ++ Tail -> + expand_env_var("${", $}, Tail, []); + "$" ++ Tail -> + expand_env_var("$", Tail, []); + "%" ++ Tail -> + expand_env_var("%", $%, Tail, []) + end. + +-spec expand_env_var(Prefix :: string(), EndMark :: char(), CmdLine :: string(), Acc :: string()) -> {string(), string()}. +expand_env_var(Prefix, EndMark, [Char | Tail], Acc) + when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse + (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) -> + expand_env_var(Prefix, EndMark, Tail, [Char | Acc]); +expand_env_var(Prefix, EndMark, [EndMark | Tail], Acc) -> + {Tail, get_env_var(Prefix, [EndMark], Acc)}; +expand_env_var(Prefix, _EndMark, CmdLine, Acc) -> + {CmdLine, Prefix ++ lists:reverse(Acc)}. + + +-spec expand_env_var(Prefix :: string(), CmdLine :: string(), Acc :: string()) -> {string(), string()}. +expand_env_var(Prefix, [Char | Tail], Acc) + when (Char >= $A andalso Char =< $Z) orelse (Char >= $a andalso Char =< $z) orelse + (Char >= $0 andalso Char =< $9) orelse (Char =:= $_) -> + expand_env_var(Prefix, Tail, [Char | Acc]); +expand_env_var(Prefix, CmdLine, Acc) -> + {CmdLine, get_env_var(Prefix, "", Acc)}. + + +-spec get_env_var(Prefix :: string(), Suffix :: string(), Acc :: string()) -> string(). +get_env_var(Prefix, Suffix, [_ | _] = Acc) -> + Name = lists:reverse(Acc), + %% Only expand valid/existing variables. + case os:getenv(Name) of + false -> Prefix ++ Name ++ Suffix; + Value -> Value + end; +get_env_var(Prefix, Suffix, []) -> + Prefix ++ Suffix. + + +-spec line_length() -> 0..?LINE_LENGTH. +line_length() -> + case io:columns() of + {ok, Columns} when Columns < ?LINE_LENGTH -> + Columns - 1; + _ -> + ?LINE_LENGTH + end. + + +-spec to_string(term()) -> string(). +to_string(List) when is_list(List) -> + case io_lib:printable_list(List) of + true -> List; + false -> io_lib:format("~p", [List]) + end; +to_string(Atom) when is_atom(Atom) -> + atom_to_list(Atom); +to_string(Value) -> + io_lib:format("~p", [Value]). diff -Nru rebar-2.0.0/src/rebar_lfe_compiler.erl rebar-2.6.0/src/rebar_lfe_compiler.erl --- rebar-2.0.0/src/rebar_lfe_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_lfe_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -30,6 +30,9 @@ -export([compile/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -45,6 +48,14 @@ %% Internal functions %% =================================================================== +info(help, compile) -> + ?CONSOLE( + "Build Lisp Flavoured Erlang (*.lfe) sources.~n" + "~n" + "Valid rebar.config options:~n" + " erl_opts is reused.'~n", + []). + compile_lfe(Source, _Target, Config) -> case code:which(lfe_comp) of non_existing -> @@ -59,11 +70,14 @@ "~n", []), ?FAIL; _ -> - Opts = [{i, "include"}, {outdir, "ebin"}, report] - ++ rebar_config:get_list(Config, erl_opts, []), + ErlOpts = rebar_utils:erl_opts(Config), + Opts = [{i, "include"}, {outdir, "ebin"}, return] ++ ErlOpts, case lfe_comp:file(Source, Opts) of - {ok, _} -> - ok; + {ok, _Mod, Ws} -> + rebar_base_compiler:ok_tuple(Config, Source, Ws); + {error, Es, Ws} -> + rebar_base_compiler:error_tuple(Config, Source, + Es, Ws, Opts); _ -> ?FAIL end diff -Nru rebar-2.0.0/src/rebar_log.erl rebar-2.6.0/src/rebar_log.erl --- rebar-2.0.0/src/rebar_log.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_log.erl 2015-06-19 16:14:28.000000000 +0000 @@ -26,53 +26,59 @@ %% ------------------------------------------------------------------- -module(rebar_log). --export([init/0, - set_level/1, get_level/0, default_level/0, - log/3]). +-export([init/1, + set_level/1, + error_level/0, + default_level/0, + log/3, + log/4, + is_verbose/1]). + +-define(ERROR_LEVEL, 0). +-define(WARN_LEVEL, 1). +-define(INFO_LEVEL, 2). +-define(DEBUG_LEVEL, 3). %% =================================================================== %% Public API %% =================================================================== -init() -> - case valid_level(rebar_config:get_global(verbose, error_level())) of - 0 -> set_level(error); - 1 -> set_level(warn); - 2 -> set_level(info); - 3 -> set_level(debug) +init(Config) -> + Verbosity = rebar_config:get_global(Config, verbose, default_level()), + case valid_level(Verbosity) of + ?ERROR_LEVEL -> set_level(error); + ?WARN_LEVEL -> set_level(warn); + ?INFO_LEVEL -> set_level(info); + ?DEBUG_LEVEL -> set_level(debug) end. 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) -> + log(standard_io, Level, Str, Args). + +log(Device, Level, Str, Args) -> {ok, LogLevel} = application:get_env(rebar, log_level), case should_log(LogLevel, Level) of true -> - io:format(log_prefix(Level) ++ Str, Args); + io:format(Device, log_prefix(Level) ++ Str, Args); false -> ok end. -default_level() -> error_level(). +error_level() -> ?ERROR_LEVEL. +default_level() -> ?WARN_LEVEL. + +is_verbose(Config) -> + rebar_config:get_xconf(Config, is_verbose, false). %% =================================================================== %% Internal functions %% =================================================================== valid_level(Level) -> - erlang:max(error_level(), erlang:min(Level, debug_level())). - -error_level() -> 0. -debug_level() -> 3. + erlang:max(?ERROR_LEVEL, erlang:min(Level, ?DEBUG_LEVEL)). should_log(debug, _) -> true; should_log(info, debug) -> false; diff -Nru rebar-2.0.0/src/rebar_metacmds.erl rebar-2.6.0/src/rebar_metacmds.erl --- rebar-2.0.0/src/rebar_metacmds.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_metacmds.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,56 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2013-2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_metacmds). + +-export(['prepare-deps'/2, + 'refresh-deps'/2]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== +'prepare-deps'(Config, _AppFile) -> + rebar:run(enable_recursion(Config), ["get-deps", "compile"]). + +'refresh-deps'(Config, _AppFile) -> + rebar:run(enable_recursion(Config), ["update-deps", "compile"]). + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, 'prepare-deps') -> + ?CONSOLE("Meta command to run 'rebar -r get-deps compile'.~n", []); +info(help, 'refresh-deps') -> + ?CONSOLE("Meta command to run 'rebar -r update-deps compile'.~n", []). + +enable_recursion(Config) -> + rebar_config:set_xconf(Config, recursive, true). diff -Nru rebar-2.0.0/src/rebar_mustache.erl rebar-2.6.0/src/rebar_mustache.erl --- rebar-2.0.0/src/rebar_mustache.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_mustache.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,230 @@ +%% The MIT License +%% +%% Copyright (c) 2009 Tom Preston-Werner +%% +%% 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. + +%% See the README at http://github.com/mojombo/mustache.erl for additional +%% documentation and usage examples. + +-module(rebar_mustache). %% v0.1.0 +-author("Tom Preston-Werner"). +-export([compile/1, compile/2, render/1, render/2, render/3, get/2, get/3, escape/1, start/1]). + +-record(mstate, {mod = undefined, + section_re = undefined, + tag_re = undefined}). + +-define(MUSTACHE_STR, "rebar_mustache"). + +compile(Body) when is_list(Body) -> + State = #mstate{}, + CompiledTemplate = pre_compile(Body, State), + % io:format("~p~n~n", [CompiledTemplate]), + % io:format(CompiledTemplate ++ "~n", []), + {ok, Tokens, _} = erl_scan:string(CompiledTemplate), + {ok, [Form]} = erl_parse:parse_exprs(Tokens), + Bindings = erl_eval:new_bindings(), + {value, Fun, _} = erl_eval:expr(Form, Bindings), + Fun; +compile(Mod) -> + TemplatePath = template_path(Mod), + compile(Mod, TemplatePath). + +compile(Mod, File) -> + code:purge(Mod), + {module, _} = code:load_file(Mod), + {ok, TemplateBin} = file:read_file(File), + Template = re:replace(TemplateBin, "\"", "\\\\\"", [global, {return,list}]), + State = #mstate{mod = Mod}, + CompiledTemplate = pre_compile(Template, State), + % io:format("~p~n~n", [CompiledTemplate]), + % io:format(CompiledTemplate ++ "~n", []), + {ok, Tokens, _} = erl_scan:string(CompiledTemplate), + {ok, [Form]} = erl_parse:parse_exprs(Tokens), + Bindings = erl_eval:new_bindings(), + {value, Fun, _} = erl_eval:expr(Form, Bindings), + Fun. + +render(Mod) -> + TemplatePath = template_path(Mod), + render(Mod, TemplatePath). + +render(Body, Ctx) when is_list(Body) -> + TFun = compile(Body), + render(undefined, TFun, Ctx); +render(Mod, File) when is_list(File) -> + render(Mod, File, dict:new()); +render(Mod, CompiledTemplate) -> + render(Mod, CompiledTemplate, dict:new()). + +render(Mod, File, Ctx) when is_list(File) -> + CompiledTemplate = compile(Mod, File), + render(Mod, CompiledTemplate, Ctx); +render(Mod, CompiledTemplate, Ctx) -> + Ctx2 = dict:store('__mod__', Mod, Ctx), + lists:flatten(CompiledTemplate(Ctx2)). + +pre_compile(T, State) -> + SectionRE = "\{\{\#([^\}]*)}}\s*(.+?){{\/\\1\}\}\s*", + {ok, CompiledSectionRE} = re:compile(SectionRE, [dotall]), + TagRE = "\{\{(#|=|!|<|>|\{)?(.+?)\\1?\}\}+", + {ok, CompiledTagRE} = re:compile(TagRE, [dotall]), + State2 = State#mstate{section_re = CompiledSectionRE, tag_re = CompiledTagRE}, + "fun(Ctx) -> " ++ + "CFun = fun(A, B) -> A end, " ++ + compiler(T, State2) ++ " end.". + +compiler(T, State) -> + Res = re:run(T, State#mstate.section_re), + case Res of + {match, [{M0, M1}, {N0, N1}, {C0, C1}]} -> + Front = string:substr(T, 1, M0), + Back = string:substr(T, M0 + M1 + 1), + Name = string:substr(T, N0 + 1, N1), + Content = string:substr(T, C0 + 1, C1), + "[" ++ compile_tags(Front, State) ++ + " | [" ++ compile_section(Name, Content, State) ++ + " | [" ++ compiler(Back, State) ++ "]]]"; + nomatch -> + compile_tags(T, State) + end. + +compile_section(Name, Content, State) -> + Mod = State#mstate.mod, + Result = compiler(Content, State), + "fun() -> " ++ + "case " ++ ?MUSTACHE_STR ++ ":get(" ++ Name ++ ", Ctx, " ++ atom_to_list(Mod) ++ ") of " ++ + "\"true\" -> " ++ + Result ++ "; " ++ + "\"false\" -> " ++ + "[]; " ++ + "List when is_list(List) -> " ++ + "[fun(Ctx) -> " ++ Result ++ " end(dict:merge(CFun, SubCtx, Ctx)) || SubCtx <- List]; " ++ + "Else -> " ++ + "throw({template, io_lib:format(\"Bad context for ~p: ~p\", [" ++ Name ++ ", Else])}) " ++ + "end " ++ + "end()". + +compile_tags(T, State) -> + Res = re:run(T, State#mstate.tag_re), + case Res of + {match, [{M0, M1}, K, {C0, C1}]} -> + Front = string:substr(T, 1, M0), + Back = string:substr(T, M0 + M1 + 1), + Content = string:substr(T, C0 + 1, C1), + Kind = tag_kind(T, K), + Result = compile_tag(Kind, Content, State), + "[\"" ++ Front ++ + "\" | [" ++ Result ++ + " | " ++ compile_tags(Back, State) ++ "]]"; + nomatch -> + "[\"" ++ T ++ "\"]" + end. + +tag_kind(_T, {-1, 0}) -> + none; +tag_kind(T, {K0, K1}) -> + string:substr(T, K0 + 1, K1). + +compile_tag(none, Content, State) -> + Mod = State#mstate.mod, + ?MUSTACHE_STR ++ ":escape(" ++ ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ "))"; +compile_tag("{", Content, State) -> + Mod = State#mstate.mod, + ?MUSTACHE_STR ++ ":get(" ++ Content ++ ", Ctx, " ++ atom_to_list(Mod) ++ ")"; +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) -> + 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), + get(list_to_atom(Key), Ctx, Mod); +get(Key, Ctx) -> + {ok, Mod} = dict:find('__mod__', Ctx), + get(Key, Ctx, Mod). + +get(Key, Ctx, Mod) when is_list(Key) -> + get(list_to_atom(Key), Ctx, Mod); +get(Key, Ctx, Mod) -> + case dict:find(Key, Ctx) of + {ok, Val} -> + % io:format("From Ctx {~p, ~p}~n", [Key, Val]), + to_s(Val); + error -> + case erlang:function_exported(Mod, Key, 1) of + true -> + Val = to_s(Mod:Key(Ctx)), + % io:format("From Mod/1 {~p, ~p}~n", [Key, Val]), + Val; + false -> + case erlang:function_exported(Mod, Key, 0) of + true -> + Val = to_s(Mod:Key()), + % io:format("From Mod/0 {~p, ~p}~n", [Key, Val]), + Val; + false -> + [] + end + end + end. + +to_s(Val) when is_integer(Val) -> + integer_to_list(Val); +to_s(Val) when is_float(Val) -> + io_lib:format("~.2f", [Val]); +to_s(Val) when is_atom(Val) -> + atom_to_list(Val); +to_s(Val) -> + Val. + +escape(HTML) -> + escape(HTML, []). + +escape([], Acc) -> + lists:reverse(Acc); +escape(["<" | Rest], Acc) -> + escape(Rest, lists:reverse("<", Acc)); +escape([">" | Rest], Acc) -> + escape(Rest, lists:reverse(">", Acc)); +escape(["&" | Rest], Acc) -> + escape(Rest, lists:reverse("&", Acc)); +escape([X | Rest], Acc) -> + escape(Rest, [X | Acc]). + +%%--------------------------------------------------------------------------- + +start([T]) -> + Out = render(list_to_atom(T)), + io:format(Out ++ "~n", []). diff -Nru rebar-2.0.0/src/rebar_neotoma_compiler.erl rebar-2.6.0/src/rebar_neotoma_compiler.erl --- rebar-2.0.0/src/rebar_neotoma_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_neotoma_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -42,6 +42,9 @@ -export([compile/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% ============================================================================ @@ -53,13 +56,26 @@ rebar_base_compiler:run(Config, [], option(doc_root, NeoOpts), ".peg", option(out_dir, NeoOpts), - option(module_ext, NeoOpts) ++ ".beam", - fun compile_neo/3, [{check_last_mod,false}]). + option(module_ext, NeoOpts) ++ ".erl", + fun compile_neo/3, [{check_last_mod, true}]). %% ============================================================================ -%% Public API +%% Internal functions %% ============================================================================ +info(help, compile) -> + ?CONSOLE( + "Build Neotoma (*.peg) sources.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + {neotoma_opts, [{doc_root, "src"}, + {out_dir, "src"}, + {source_ext, ".peg"}, + {module_ext, ""}]} + ]). + neotoma_opts(Config) -> rebar_config:get(Config, neotoma_opts, []). diff -Nru rebar-2.0.0/src/rebar_otp_app.erl rebar-2.6.0/src/rebar_otp_app.erl --- rebar-2.0.0/src/rebar_otp_app.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_otp_app.erl 2015-06-19 16:14:28.000000000 +0000 @@ -29,6 +29,9 @@ -export([compile/2, clean/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). %% =================================================================== @@ -39,27 +42,27 @@ %% 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]) @@ -82,37 +85,56 @@ ok end. - %% =================================================================== %% Internal functions %% =================================================================== +info(help, compile) -> + info_help("Validate .app file"); +info(help, clean) -> + info_help("Delete .app file if generated from .app.src"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n", + [ + Description, + {validate_app_modules, true} + ]). + 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}), + %% systools:make_relup/4 fails with {missing_param, registered} + %% without a 'registered' value. + A3 = ensure_registered(A2), + %% Build the final spec as a string - Spec = io_lib:format("~p.\n", [{application, AppName, A2}]), + Spec = io_lib:format("~p.\n", [{application, AppName, A3}]), %% 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", @@ -187,3 +209,12 @@ ebin_modules() -> lists:sort([rebar_utils:beam_to_mod("ebin", N) || N <- rebar_utils:beams("ebin")]). + +ensure_registered(AppData) -> + case lists:keyfind(registered, 1, AppData) of + false -> + [{registered, []} | AppData]; + {registered, _} -> + %% We could further check whether the value is a list of atoms. + AppData + end. diff -Nru rebar-2.0.0/src/rebar_otp_appup.erl rebar-2.6.0/src/rebar_otp_appup.erl --- rebar-2.0.0/src/rebar_otp_appup.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_otp_appup.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,89 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Luis Rascao (luis.rascao@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_otp_appup). + +-export([compile/2, + clean/2]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec compile(rebar_config:config(), file:filename()) -> 'ok'. +compile(Config, _AppFile) -> + %% If we get an *.appup.src file, it needs to be pre-processed and + %% written out as a ebin/*.appup file. + Files = rebar_utils:find_files_by_ext("src", ".appup.src"), + Targets = [filename:join("ebin", + filename:rootname(filename:basename(F))) + || F <- Files], + rebar_base_compiler:run(Config, [], + lists:zip(Files, Targets), + fun preprocess/3, + [{check_last_mod, true}]). + +clean(Config, _AppFile) -> + Files = rebar_utils:find_files_by_ext("ebin", ".appup"), + rebar_file_utils:delete_each([Files]), + {ok, Config}. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, compile) -> + ?CONSOLE("Validate .appup.src file", []); +info(help, clean) -> + ?CONSOLE("Delete .appup file if generated from .appup.src", []). + +preprocess(SourceFile, TargetFile, _Config) -> + %% Perform basic validation on the appup file + %% i.e. if a consult succeeds and basic appup + %% structure exists. + case rebar_config:consult_file(SourceFile) of + %% The .appup syntax is described in + %% http://erlang.org/doc/man/appup.html. + {ok, [{_Vsn, UpFromVsn, DownToVsn} = AppUp]} + when is_list(UpFromVsn), is_list(DownToVsn) -> + case file:write_file( + TargetFile, + lists:flatten(io_lib:format("~p.", [AppUp]))) of + {error, Reason} -> + ?ABORT("Failed writing to target file ~s due to ~s", + [TargetFile, Reason]); + ok -> ok + end; + {error, Reason} -> + ?ABORT("Failed to compile ~s: ~p~n", [SourceFile, Reason]); + _ -> + ?ABORT("Failed to compile ~s, not an appup~n", [SourceFile]) + end. diff -Nru rebar-2.0.0/src/rebar_port_compiler.erl rebar-2.6.0/src/rebar_port_compiler.erl --- rebar-2.0.0/src/rebar_port_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_port_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -27,8 +27,11 @@ -module(rebar_port_compiler). -export([compile/2, - clean/2, - setup_env/1]). + clean/2]). + +%% for internal use only +-export([setup_env/1, + info/2]). -include("rebar.hrl"). @@ -86,37 +89,22 @@ %% "$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 - - SharedEnv = rebar_config:get_env(Config, ?MODULE), + case get_specs(Config, AppFile) of + [] -> + ok; + Specs -> + SharedEnv = rebar_config:get_env(Config, rebar_deps) ++ + 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]), @@ -147,23 +135,17 @@ 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 - + case get_specs(Config, AppFile) of + [] -> + ok; + Specs -> lists:foreach(fun(#spec{target=Target, objects=Objects}) -> rebar_file_utils:delete_each([Target]), - rebar_file_utils:delete_each(Objects) + rebar_file_utils:delete_each(Objects), + rebar_file_utils:delete_each(port_deps(Objects)) end, Specs) - end. + end, + ok. setup_env(Config) -> setup_env(Config, []). @@ -172,20 +154,48 @@ %% Internal functions %% =================================================================== +info(help, compile) -> + info_help("Build port sources"); +info(help, clean) -> + info_help("Delete port build results"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n", + [ + Description, + {port_env, [{"CFLAGS", "$CFLAGS -Ifoo"}, + {"freebsd", "LDFLAGS", "$LDFLAGS -lfoo"}]}, + {port_specs, [{"priv/so_name.so", ["c_src/*.c"]}, + {"linux", "priv/hello_linux", ["c_src/hello_linux.c"]}, + {"linux", "priv/hello_linux", ["c_src/*.c"], [{env, []}]}]} + ]). + setup_env(Config, ExtraEnv) -> %% Extract environment values from the config (if specified) and %% 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, []), + + %% Get any port-specific envs; use port_env first and then fallback + %% to port_envs for compatibility + RawPortEnv = rebar_config:get_list(Config, port_env, + rebar_config:get_list(Config, port_envs, [])), + + 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,36 +208,72 @@ %% == 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 true -> - ?CONSOLE("Compiling ~s\n", [Source]), Template = select_compile_template(Type, compiler(Ext)), - rebar_utils:sh(expand_command(Template, Env, Source, Bin), - [{env, Env}]), - compile_each(Rest, Type, Env, [Bin | NewBins]); + Cmd = expand_command(Template, Env, Source, Bin), + ShOpts = [{env, Env}, return_on_error, {use_stdout, false}], + 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(Config, Source, Cmd, ShOpts) -> + case rebar_utils:sh(Cmd, ShOpts) of + {error, {_RC, RawError}} -> + 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]), + ?FAIL; + {ok, Output} -> + ?CONSOLE("Compiling ~s\n", [Source]), + ?CONSOLE("~s", [Output]) end. needs_compile(Source, Bin) -> - %% TODO: Generate depends using gcc -MM so we can also - %% check for include changes - filelib:last_modified(Bin) < filelib:last_modified(Source). + needs_link(Bin, [Source|bin_deps(Bin)]). + +%% NOTE: This relies on -MMD being passed to the compiler and returns an +%% empty list if the .d file is not available. This means header deps are +%% ignored on win32. +bin_deps(Bin) -> + [DepFile] = port_deps([Bin]), + case file:read_file(DepFile) of + {ok, Deps} -> + Ds = parse_bin_deps(list_to_binary(Bin), Deps), + ?DEBUG("Deps of ~p: ~p\n", [Bin, Ds]), + Ds; + {error, Err} -> + ?DEBUG("Skipping deps parse of ~s: ~p\n", [DepFile, Err]), + [] + end. + +parse_bin_deps(Bin, Deps) -> + Sz = size(Bin), + <> = Deps, + Ds = re:split(X, "\\s*\\\\\\R\\s*|\\s+", [{return, binary}]), + [D || D <- Ds, D =/= <<>>]. needs_link(SoName, []) -> filelib:last_modified(SoName) == 0; @@ -247,18 +293,37 @@ %% 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) -> - Filtered = filter_port_specs(PortSpecs), - OsType = os:type(), - {[], [get_port_spec(Config, OsType, Spec) || Spec <- Filtered]}. + Specs = case rebar_config:get_local(Config, port_specs, []) of + [] -> + %% No spec provided. Construct a spec + %% from old-school so_name and sources + [port_spec_from_legacy(Config, AppFile)]; + PortSpecs -> + Filtered = filter_port_specs(PortSpecs), + OsType = os:type(), + [get_port_spec(Config, OsType, Spec) || Spec <- Filtered] + end, + [S || S <- Specs, S#spec.sources /= []]. + +port_spec_from_legacy(Config, AppFile) -> + %% Get the target from the so_name variable + Target = case rebar_config:get(Config, so_name, undefined) of + undefined -> + %% Generate a sensible default from app file + {_, AppName} = rebar_app_utils:app_name(Config, AppFile), + filename:join("priv", + lists:concat([AppName, "_drv.so"])); + AName -> + %% Old form is available -- use it + filename:join("priv", AName) + end, + %% Get the list of source files from port_sources + Sources = port_sources(rebar_config:get_list(Config, port_sources, + ["c_src/*.c"])), + #spec { type = target_type(Target), + target = maybe_switch_extension(os:type(), Target), + sources = Sources, + objects = port_objects(Sources) }. filter_port_specs(Specs) -> [S || S <- Specs, filter_port_spec(S)]. @@ -289,6 +354,9 @@ port_objects(SourceFiles) -> [replace_extension(O, ".o") || O <- SourceFiles]. +port_deps(SourceFiles) -> + [replace_extension(O, ".d") || O <- SourceFiles]. + port_opts(Config, Opts) -> [port_opt(Config, O) || O <- Opts]. @@ -309,58 +377,6 @@ _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 == %% @@ -431,7 +447,7 @@ expand_vars_loop(Recurse, [], Vars, Count-1); expand_vars_loop([{K, V} | Rest], Recurse, Vars, Count) -> %% Identify the variables that need expansion in this value - ReOpts = [global, {capture, all_but_first, list}], + ReOpts = [global, {capture, all_but_first, list}, unicode], case re:run(V, "\\\${?(\\w+)}?", ReOpts) of {match, Matches} -> %% Identify the unique variables that need to be expanded @@ -473,8 +489,7 @@ expand_command(TmplName, Env, InFiles, OutFile) -> Cmd0 = proplists:get_value(TmplName, Env), Cmd1 = rebar_utils:expand_env_variable(Cmd0, "PORT_IN_FILES", InFiles), - Cmd2 = rebar_utils:expand_env_variable(Cmd1, "PORT_OUT_FILE", OutFile), - re:replace(Cmd2, "\\\$\\w+|\\\${\\w+}", "", [global, {return, list}]). + rebar_utils:expand_env_variable(Cmd1, "PORT_OUT_FILE", OutFile). %% %% Given a string, determine if it is expandable @@ -485,35 +500,6 @@ 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. @@ -534,11 +520,21 @@ lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]). os_env() -> - Os = [list_to_tuple(re:split(S, "=", [{return, list}, {parts, 2}])) || - S <- os:getenv()], + ReOpts = [{return, list}, {parts, 2}, unicode], + Os = [list_to_tuple(re:split(S, "=", ReOpts)) || + S <- lists:filter(fun discard_deps_vars/1, os:getenv())], %% Drop variables without a name (win32) [T1 || {K, _V} = T1 <- Os, K =/= []]. +%% +%% To avoid having multiple repetitions of the same environment variables +%% (ERL_LIBS), avoid exporting any variables that may cause conflict with +%% those exported by the rebar_deps module (ERL_LIBS, REBAR_DEPS_DIR) +%% +discard_deps_vars("ERL_LIBS=" ++ _Value) -> false; +discard_deps_vars("REBAR_DEPS_DIR=" ++ _Value) -> false; +discard_deps_vars(_Var) -> true. + select_compile_template(drv, Compiler) -> select_compile_drv_template(Compiler); select_compile_template(exe, Compiler) -> @@ -587,15 +583,15 @@ "$CC -c $CFLAGS $EXE_CFLAGS $PORT_IN_FILES -o $PORT_OUT_FILE"}, {"EXE_LINK_TEMPLATE", "$CC $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS -o $PORT_OUT_FILE"}, - {"DRV_CFLAGS" , "-g -Wall -fPIC $ERL_CFLAGS"}, + {"DRV_CFLAGS" , "-g -Wall -fPIC -MMD $ERL_CFLAGS"}, {"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"}, - {"EXE_CFLAGS" , "-g -Wall -fPIC $ERL_CFLAGS"}, + {"EXE_CFLAGS" , "-g -Wall -fPIC -MMD $ERL_CFLAGS"}, {"EXE_LDFLAGS", "$ERL_LDFLAGS"}, - {"ERL_CFLAGS", lists:concat([" -I", erl_interface_dir(include), - " -I", filename:join(erts_dir(), "include"), - " "])}, - {"ERL_EI_LIBDIR", erl_interface_dir(lib)}, + {"ERL_CFLAGS", lists:concat([" -I\"", erl_interface_dir(include), + "\" -I\"", filename:join(erts_dir(), "include"), + "\" "])}, + {"ERL_EI_LIBDIR", lists:concat(["\"", erl_interface_dir(lib), "\""])}, {"ERL_LDFLAGS" , " -L$ERL_EI_LIBDIR -lerl_interface -lei"}, {"ERLANG_ARCH" , rebar_utils:wordsize()}, {"ERLANG_TARGET", rebar_utils:get_arch()}, @@ -608,18 +604,42 @@ {"solaris.*-64$", "CXXFLAGS", "-D_REENTRANT -m64 $CXXFLAGS"}, {"solaris.*-64$", "LDFLAGS", "-m64 $LDFLAGS"}, + %% Linux specific flags for multiarch + {"linux.*-64$", "CFLAGS", "-m64 $CFLAGS"}, + {"linux.*-64$", "CXXFLAGS", "-m64 $CXXFLAGS"}, + {"linux.*-64$", "LDFLAGS", "$LDFLAGS"}, + %% OS X Leopard flags for 64-bit {"darwin9.*-64$", "CFLAGS", "-m64 $CFLAGS"}, {"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 -Nru rebar-2.0.0/src/rebar_protobuffs_compiler.erl rebar-2.6.0/src/rebar_protobuffs_compiler.erl --- rebar-2.0.0/src/rebar_protobuffs_compiler.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_protobuffs_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -26,8 +26,12 @@ %% ------------------------------------------------------------------- -module(rebar_protobuffs_compiler). --export([compile/2, - clean/2]). +-export([key/0, + proto_compile/3, + proto_clean/3]). + +%% for internal use only +-export([proto_info/2]). -include("rebar.hrl"). @@ -35,47 +39,49 @@ %% Public API %% =================================================================== -compile(_Config, _AppFile) -> - case rebar_utils:find_files("src", ".*\\.proto$") of - [] -> - ok; - FoundFiles -> - %% Check for protobuffs library -- if it's not present, fail - %% since we have.proto files that need building - case protobuffs_is_present() of - true -> - %% Build a list of output files - { Proto, Beam, Hrl } - Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} || - Proto <- FoundFiles], - - %% Compile each proto file - compile_each(Targets); - false -> - ?ERROR("Protobuffs library not present in code path!\n", - []), - ?FAIL - end - end. +key() -> + protobuffs. +proto_compile(Config, _AppFile, ProtoFiles) -> + %% Check for protobuffs library -- if it's not present, fail + %% since we have.proto files that need building + case protobuffs_is_present() of + true -> + %% Build a list of output files - { Proto, Beam, Hrl } + Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} || + Proto <- ProtoFiles], -clean(_Config, _AppFile) -> - %% Get a list of generated .beam and .hrl files and then delete them - Protos = rebar_utils:find_files("src", ".*\\.proto$"), - BeamFiles = [fq_beam_file(F) || F <- Protos], - HrlFiles = [fq_hrl_file(F) || F <- Protos], - Targets = BeamFiles ++ HrlFiles, - case Targets of - [] -> - ok; - _ -> - delete_each(Targets) + %% Compile each proto file + compile_each(Config, Targets); + false -> + ?ERROR("Protobuffs library not present in code path!\n", + []), + ?FAIL end. +proto_clean(_Config, _AppFile, ProtoFiles) -> + %% Get a list of generated .beam and .hrl files and then delete them + BeamFiles = [fq_beam_file(F) || F <- ProtoFiles], + HrlFiles = [fq_hrl_file(F) || F <- ProtoFiles], + Targets = BeamFiles ++ HrlFiles, + delete_each(Targets). %% =================================================================== %% Internal functions %% =================================================================== +proto_info(help, compile) -> + info_help(); +proto_info(help, clean) -> + info_help(). + +info_help() -> + ?CONSOLE( + "Valid rebar.config options:~n" + " erl_opts is passed as compile_flags to " + "protobuffs_compile:scan_file/2~n", + []). + protobuffs_is_present() -> code:which(protobuffs_compile) =/= non_existing. @@ -95,13 +101,15 @@ 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/ @@ -113,14 +121,14 @@ ok = rebar_file_utils:mv(Hrl, "include"), ok; Other -> - ?ERROR("Protobuff compile of ~s failed: ~p\n", + ?ERROR("Protobuffs compile of ~s failed: ~p\n", [Proto, Other]), ?FAIL end; false -> ok end, - compile_each(Rest). + compile_each(Config, Rest). delete_each([]) -> ok; diff -Nru rebar-2.0.0/src/rebar_proto_compiler.erl rebar-2.6.0/src/rebar_proto_compiler.erl --- rebar-2.0.0/src/rebar_proto_compiler.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_proto_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,158 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_proto_compiler). + +-export([compile/2, + clean/2]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +-record(proto_compiler, + {key :: atom(), % Corresponds to the {proto_compiler,Key} + module :: atom() + }). + +%% =================================================================== +%% Public API +%% =================================================================== + +find_proto_files(ProtoDirs) -> + lists:foldl(fun(ProtoDir, Acc) -> + rebar_utils:find_files_by_ext(ProtoDir, ".proto") ++ Acc + end, + [], ProtoDirs). + +compile(Config, AppFile) -> + %% Find a compiler for protocol buffers, + %% use that for compiling protocol buffers + {CompilerModule, ProtoDirs} = select_proto_compiler_and_dir(Config), + case find_proto_files(ProtoDirs) of + [] -> + ok; + Protos -> + %% Ask the proto compiler to compile the .proto files. + CompilerModule:proto_compile(Config, AppFile, Protos) + end. + +clean(Config, AppFile) -> + %% Find a compiler for protocol buffers, + %% use that for clean protocol buffers + {CompilerModule, ProtoDirs} = select_proto_compiler_and_dir(Config), + %% Get a list of generated .beam and .hrl files and then delete them + case find_proto_files(ProtoDirs) of + [] -> + ok; + Protos -> + %% Ask the proto compiler to clean the .proto files. + CompilerModule:proto_clean(Config, AppFile, Protos) + end. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, compile) -> + info_help("Build protocol buffer (*.proto) sources", compile); +info(help, clean) -> + info_help("Delete protocol buffer (*.proto) build results", clean). + +info_help(GeneralDescr, Cmd) -> + ?CONSOLE( + "~s.~n" + ++ "~n" + ++ "Valid rebar.config options:~n" + ++ " {proto_opts, [~n" + ++ " {compiler, Compiler},~n" + ++ " {src_dirs, [Dir]}~n" + ++ " ]}~n" + ++ "The following protocol buffer compilers are available:~n" + ++ "~s~n", + [GeneralDescr, format_proto_compiler_list()]), + %% Print info for each proto compiler + _ = [begin + ?CONSOLE("--- ~p ---~n", [Key]), + CompilerModule:proto_info(help, Cmd) + end + || #proto_compiler{key=Key, + module=CompilerModule} <- find_proto_compilers()], + ok. + +get_default_compiler() -> + protobuffs. + +find_proto_compilers() -> + {ok, RebarModuleGroups} = application:get_env(rebar, modules), + {app_dir, Modules} = lists:keyfind(app_dir, 1, RebarModuleGroups), + [#proto_compiler{key = M:key(), + module = M} + || M <- Modules, + is_proto_compiler_module(M)]. + +is_proto_compiler_module(Module) -> + case code:ensure_loaded(Module) of + {module, Module} -> + lists:all(fun({Function, Arity}) -> + erlang:function_exported(Module, Function, Arity) + end, + [{key, 0}, + {proto_compile, 3}, + {proto_clean, 3}, + {proto_info, 2}]); + _ -> + false + end. + +select_proto_compiler_and_dir(Config) -> + Default = get_default_compiler(), + ProtoOpts = rebar_config:get_local(Config, proto_opts, []), + Key = proplists:get_value(compiler, ProtoOpts, Default), + ProtoDirs = proplists:get_value(src_dirs, ProtoOpts, ["src"]), + AvailCompilers = find_proto_compilers(), + CompilerModule = case lists:keyfind(Key, #proto_compiler.key, AvailCompilers) of + #proto_compiler{module=Module} -> + Module; + false -> + ?ABORT("No such protocol buffer compiler known, '~s'~n" + ++ "The following are known:~n" + ++ "~s~n", + [Key, format_proto_compiler_list()]) + end, + {CompilerModule, ProtoDirs}. + +format_proto_compiler_list() -> + Default = get_default_compiler(), + Keys = [Key || #proto_compiler{key=Key} <- find_proto_compilers()], + Annotations = [if Key == Default -> " (default)"; + true -> "" + end + || Key <- Keys], + lists:flatten( + [?FMT(" ~p~s~n", [Key, Annotation]) + || {Key, Annotation} <- lists:zip(Keys, Annotations)]). diff -Nru rebar-2.0.0/src/rebar_proto_gpb_compiler.erl rebar-2.6.0/src/rebar_proto_gpb_compiler.erl --- rebar-2.0.0/src/rebar_proto_gpb_compiler.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_proto_gpb_compiler.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,142 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_proto_gpb_compiler). + +-export([key/0, + proto_compile/3, + proto_clean/3]). + +%% for internal use only +-export([proto_info/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +key() -> + gpb. + +proto_compile(Config, _AppFile, Files) -> + %% Check for gpb library -- if it's not present, fail + %% since we have.proto files that need building + case gpb_is_present() of + true -> + GpbOpts = user_gpb_opts(Config), + Targets = [filename:join("src", target_filename(F, GpbOpts)) + || F <- Files], + rebar_base_compiler:run(Config, [], + lists:zip(Files, Targets), + fun compile_gpb/3, + [{check_last_mod, true}]); + false -> + ?ERROR("The gpb library is not present in code path!\n", []), + ?FAIL + end. + +target_filename(ProtoFileName, GpbOpts) -> + ModulePrefix = proplists:get_value(module_name_prefix, GpbOpts, ""), + ModuleSuffix = proplists:get_value(module_name_suffix, GpbOpts, ""), + Base = filename:basename(ProtoFileName, ".proto"), + ModulePrefix ++ Base ++ ModuleSuffix ++ ".erl". + +proto_clean(Config, _AppFile, ProtoFiles) -> + GpbOpts = user_gpb_opts(Config) ++ default_dest_opts(), + rebar_file_utils:delete_each( + [beam_file(F, GpbOpts) || F <- ProtoFiles] + ++ [erl_file(F, GpbOpts) || F <- ProtoFiles] + ++ [hrl_file(F, GpbOpts) || F <- ProtoFiles]), + ok. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +proto_info(help, compile) -> + ?CONSOLE( + " gpb_opts is passed as options to gpb_compile:file/2.~n" + " erl_opts is used when compiling the generated erlang files,~n" + " so you might want to add an include path to gpb here,~n" + " for gpb.hrl, or else use the include_as_lib gpb_opts option,~n" + " or the defs_as_proplists gpb_opts option.~n", + []); +proto_info(help, clean) -> + ?CONSOLE("", []). + +gpb_is_present() -> + code:which(gpb) =/= non_existing. + +user_gpb_opts(Config) -> + rebar_config:get_local(Config, gpb_opts, []). + +default_dest_opts() -> + [{o_erl, "src"}, {o_hrl, "include"}]. + +compile_gpb(Source, _Target, Config) -> + SourceFullPath = filename:absname(Source), + GpbOpts = user_gpb_opts(Config) ++ default_dest_opts() + ++ default_include_opts(SourceFullPath), + ok = filelib:ensure_dir(filename:join("ebin", "dummy")), + ok = filelib:ensure_dir(filename:join("include", "dummy")), + case gpb_compile:file(SourceFullPath, GpbOpts) of + ok -> + ok; + {error, Reason} -> + ReasonStr = gpb_compile:format_error(Reason), + ?ERROR("Failed to compile ~s: ~s~n", [SourceFullPath, ReasonStr]), + ?FAIL + end. + +default_include_opts(SourceFullPath) -> + [{i,filename:dirname(SourceFullPath)}]. + +beam_file(ProtoFile, GpbOpts) -> + proto_filename_to_path("ebin", ProtoFile, ".beam", GpbOpts). + +erl_file(ProtoFile, GpbOpts) -> + ErlOutDir = get_erl_outdir(GpbOpts), + proto_filename_to_path(ErlOutDir, ProtoFile, ".erl", GpbOpts). + +hrl_file(ProtoFile, GpbOpts) -> + HrlOutDir = get_hrl_outdir(GpbOpts), + proto_filename_to_path(HrlOutDir, ProtoFile, ".hrl", GpbOpts). + +proto_filename_to_path(Dir, ProtoFile, NewExt, GpbOpts) -> + BaseNoExt = filename:basename(ProtoFile, ".proto"), + Prefix = proplists:get_value(module_name_prefix, GpbOpts, ""), + Suffix = proplists:get_value(module_name_suffix, GpbOpts, ""), + filename:join([Dir, Prefix ++ BaseNoExt ++ Suffix ++ NewExt]). + +get_erl_outdir(Opts) -> + proplists:get_value(o_erl, Opts, get_outdir(Opts)). + +get_hrl_outdir(Opts) -> + proplists:get_value(o_hrl, Opts, get_outdir(Opts)). + +get_outdir(Opts) -> + proplists:get_value(o, Opts, "."). diff -Nru rebar-2.0.0/src/rebar_qc.erl rebar-2.6.0/src/rebar_qc.erl --- rebar-2.0.0/src/rebar_qc.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_qc.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,219 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2011-2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_qc). + +-export([qc/2, + triq/2, + eqc/2, + clean/2]). + +%% for internal use only +-export([info/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). + +clean(_Config, _File) -> + rebar_file_utils:rm_rf(?QC_DIR). + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, qc) -> + ?CONSOLE( + "Test QuickCheck properties.~n" + "~n" + "Valid rebar.config options:~n" + " {qc_opts, [{qc_mod, module()}, Options]}~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n" + "Valid command line options:~n" + " compile_only=true (Compile but do not test properties)", + [ + {qc_compile_opts, []}, + {qc_first_files, []}, + {cover_enabled, false}, + {cover_print_enabled, false}, + {cover_export_enabled, false} + ]); +info(help, clean) -> + Description = ?FMT("Delete QuickCheck test dir (~s)", [?QC_DIR]), + ?CONSOLE("~s.~n", [Description]). + +-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_config:get_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 = rebar_utils:cleanup_code_path(CodePath), + ?CONSOLE("Compiled modules for qc~n", []); + false -> + run1(QC, QCOpts, Config, CodePath, SrcErls) + end. + +run1(QC, QCOpts, Config, CodePath, SrcErls) -> + + AllBeamFiles = rebar_utils:beams(?QC_DIR), + AllModules = [rebar_utils:beam_to_mod(?QC_DIR, N) + || N <- AllBeamFiles], + PropMods = find_prop_mods(), + FilteredModules = AllModules -- PropMods, + + SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], + + {ok, CoverLog} = rebar_cover_utils:init(Config, AllBeamFiles, qc_dir()), + + TestModule = fun(M) -> qc_module(QC, QCOpts, M) end, + QCResult = lists:flatmap(TestModule, PropMods), + + rebar_cover_utils:perform_cover(Config, FilteredModules, SrcModules, + qc_dir()), + rebar_cover_utils:close(CoverLog), + ok = rebar_cover_utils:exit(), + + true = rebar_utils:cleanup_code_path(CodePath), + + case QCResult of + [] -> + 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, [], M) -> QC:module(M); +qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M). + +find_prop_mods() -> + Beams = rebar_utils:find_files_by_ext(?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 -Nru rebar-2.0.0/src/rebar_reltool.erl rebar-2.6.0/src/rebar_reltool.erl --- rebar-2.0.0/src/rebar_reltool.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_reltool.erl 2015-06-19 16:14:28.000000000 +0000 @@ -30,6 +30,9 @@ overlay/2, clean/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). -include_lib("kernel/include/file.hrl"). @@ -37,12 +40,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,7 +59,7 @@ %% Finally, run reltool case catch(run_reltool(Server, Config, ReltoolConfig)) of ok -> - ok; + {ok, Config}; {error, failed} -> ?FAIL; Other2 -> @@ -64,30 +67,57 @@ ?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 %% =================================================================== +info(help, generate) -> + info_help("Build release with reltool"); +info(help, clean) -> + info_help("Delete release"); +info(help, overlay) -> + info_help("Run reltool overlays only"). + +info_help(Description) -> + ?CONSOLE( + "~s.~n" + "~n" + "Valid rebar.config options:~n" + " ~n" + "Valid reltool.config options:~n" + " {sys, []}~n" + " {target_dir, \"target\"}~n" + " {overlay_vars, \"overlay\"}~n" + " {overlay, []}~n" + "Valid command line options:~n" + " target_dir=target~n" + " overlay_vars=VarsFile~n" + " dump_spec=1 (write reltool target spec to reltool.spec)~n", + [ + Description + ]). + 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 " "to be installed!", []); Path -> - ReltoolVsn = filename:basename(Path), + ReltoolVsn = reltool_vsn(Path), case ReltoolVsn < "reltool-0.5.2" of true -> ?ABORT("Reltool support requires at least reltool-0.5.2; " @@ -97,8 +127,41 @@ end end. -process_overlay(ReltoolConfig) -> - TargetDir = rebar_rel_utils:get_target_dir(ReltoolConfig), +reltool_vsn(ReltoolPath) -> + AppFile = filename:join([ReltoolPath, "ebin", "reltool.app"]), + case filelib:is_regular(AppFile) of + true -> + reltool_vsn_from_app(); + false -> + reltool_vsn_from_path(ReltoolPath) + end. + +reltool_vsn_from_app() -> + ok = case application:load(reltool) of + ok -> + ok; + {error, {already_loaded, reltool}} -> + ok + end, + {ok, Vsn} = application:get_key(reltool, vsn), + "reltool-" ++ Vsn. + +%% NOTE: OTP releases prior to R14B did not install +%% lib/reltool-x.y.z/ebin/reltool.app. Therefore, if we cannot find the app +%% file, we have to resort to getting the version string from reltool's lib_dir +%% path. This usually works, but as reported in +%% https://github.com/rebar/rebar/issues/415 there can be installations without +%% version strings in lib_dir paths. Given R13's age and that it's unusual to +%% have vsn-less lib_dir paths, this shouldn't be a problem. +%% +%% TODO: Once we require at least R14B04 (didn't check for existence of +%% ebin/reltool.app in R14 releases older than R14B04), simplify reltool_vsn/1 +%% to get the version string only from the app key. +reltool_vsn_from_path(ReltoolPath) -> + filename:basename(ReltoolPath). + +process_overlay(Config, ReltoolConfig) -> + TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), {_BootRelName, BootRelVsn} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig), @@ -108,23 +171,21 @@ 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), %% Finally, overlay the files specified by the overlay section - case lists:keyfind(overlay, 1, ReltoolConfig) of - {overlay, Overlay} when is_list(Overlay) -> + case overlay_files(ReltoolConfig) of + [] -> + ok; + Overlay -> execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(), - TargetDir); - false -> - ?INFO("No {overlay, [...]} found in reltool.config.\n", []); - _ -> - ?ABORT("{overlay, [...]} entry in reltool.config " - "must be a list.\n", []) + TargetDir) end. %% @@ -134,27 +195,38 @@ %% 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) -> - BaseVars = load_vars_file(proplists:get_value(overlay_vars, ReltoolConfig)), - OverrideVars = load_vars_file(rebar_config:get_global(overlay_vars, undefined)), - M = fun(_Key, _Base, Override) -> Override end, +overlay_vars(Config, Vars0, ReltoolConfig) -> + BaseVars = load_vars_file( + [proplists:get_value(overlay_vars, ReltoolConfig)]), + OverlayVars = rebar_config:get_global(Config, overlay_vars, []), + OverrideVars = load_vars_file(string:tokens(OverlayVars, ",")), + M = fun merge_overlay_var/3, dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars). +merge_overlay_var(_Key, _Base, Override) -> Override. + %% %% If a filename is provided, construct a dict of terms %% -load_vars_file(undefined) -> +load_vars_file([undefined]) -> dict:new(); -load_vars_file(File) -> - case file:consult(File) of +load_vars_file([]) -> + dict:new(); +load_vars_file(Files) -> + load_vars_file(Files, dict:new()). + +load_vars_file([], Dict) -> + Dict; +load_vars_file([File | Files], BaseVars) -> + case rebar_config:consult_file(File) of {ok, Terms} -> - dict:from_list(Terms); + OverrideVars = dict:from_list(Terms), + M = fun merge_overlay_var/3, + load_vars_file(Files, dict:merge(M, BaseVars, OverrideVars)); {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 +259,18 @@ 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,20 +286,19 @@ 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); @@ -243,9 +313,8 @@ ?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); @@ -254,6 +323,27 @@ end. +overlay_files(ReltoolConfig) -> + Original = case lists:keyfind(overlay, 1, ReltoolConfig) of + {overlay, Overlay} when is_list(Overlay) -> + Overlay; + false -> + ?INFO("No {overlay, [...]} found in reltool.config.\n", + []), + []; + _ -> + ?ABORT("{overlay, [...]} entry in reltool.config " + "must be a list.\n", []) + end, + SlimAddition = case rebar_rel_utils:get_excl_lib_tuple(ReltoolConfig) of + {excl_lib, otp_root} -> + [{create, "releases/{{rel_vsn}}/runner_script.data", + "slim\n"}]; + false -> + [] + end, + Original ++ SlimAddition. + %% TODO: Merge functionality here with rebar_templater execute_overlay([], _Vars, _BaseDir, _TargetDir) -> @@ -346,10 +436,22 @@ 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 -Nru rebar-2.0.0/src/rebar_rel_utils.erl rebar-2.6.0/src/rebar_rel_utils.erl --- rebar-2.0.0/src/rebar_rel_utils.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_rel_utils.erl 2015-06-19 16:14:28.000000000 +0000 @@ -33,13 +33,15 @@ 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, + get_rel_file_path/3, + load_config/2, get_sys_tuple/1, - get_target_dir/1, - get_root_dir/1, - get_target_parent_dir/1]). + get_excl_lib_tuple/1, + get_target_dir/2, + get_root_dir/2, + get_target_parent_dir/2]). -include("rebar.hrl"). @@ -105,14 +107,22 @@ %% Get rel file path from name and 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"]). + PVer = get_permanent_version(Path), + get_rel_file_path(Name, Path, PVer). + +get_rel_file_path(Name, Path, Version) -> + Dir = filename:join([Path, "releases", Version]), + Path1 = filename:join([Dir, Name ++ "_" ++ Version ++".rel"]), + Path2 = filename:join([Dir, Name ++ ".rel"]), + case {filelib:is_file(Path1), filelib:is_file(Path2)} of + {true, _} -> Path1; + {_, true} -> Path2; + _ -> ?ABORT("can not find .rel file for version ~p~n", [Version]) + end. %% 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,12 +133,12 @@ %% %% 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", + ?ABORT("Failed to load expected config from ~s: ~p~n", [ReltoolFile, Other]) end. @@ -141,15 +151,22 @@ {sys, _} = SysTuple -> SysTuple; false -> - ?ABORT("Failed to find {sys, [...]} tuple in reltool.config.", []) + ?ABORT("Failed to find {sys, [...]} tuple in reltool.config~n", []) end. %% +%% Look for the {excl_lib, ...} tuple in sys tuple of the reltool.config file. +%% Without this present, return false. +%% +get_excl_lib_tuple(ReltoolConfig) -> + lists:keyfind(excl_lib, 1, element(2, 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 +184,8 @@ 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 +195,10 @@ %% 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 +234,36 @@ 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}. + +%% get permanent version from start_erl.data +get_permanent_version(Path) -> + DataFile = filename:join([Path, "releases", "start_erl.data"]), + case file:read_file(DataFile) of + {ok, DataBin} -> + [_, Version] = string:tokens( + string:strip(binary_to_list(DataBin), right, $\n), + " "), + Version; + {error, enoent} -> + ?ABORT("~s is missing~n", [DataFile]) + end. diff -Nru rebar-2.0.0/src/rebar_require_vsn.erl rebar-2.6.0/src/rebar_require_vsn.erl --- rebar-2.0.0/src/rebar_require_vsn.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_require_vsn.erl 2015-06-19 16:14:28.000000000 +0000 @@ -33,6 +33,10 @@ -export([compile/2, eunit/2]). +%% for internal use only +-export([info/2, + version_tuple/3]). + %% =================================================================== %% Public API %% =================================================================== @@ -43,7 +47,34 @@ eunit(Config, _) -> check_versions(Config). +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +info(help, compile) -> + info_help(); +info(help, eunit) -> + info_help(). + +info_help() -> + ?CONSOLE( + "Check required ERTS or OTP release version.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n", + [ + {require_erts_vsn, ".*"}, + {require_otp_vsn, ".*"}, + {require_min_otp_vsn, ".*"} + ]). + check_versions(Config) -> + ShouldAbort = case rebar_config:get_xconf(Config, keep_going, false) of + true -> keep_going; + false -> abort + end, ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"), ReOpts = [{capture, none}], case re:run(erlang:system_info(version), ErtsRegex, ReOpts) of @@ -51,8 +82,10 @@ ?DEBUG("Matched required ERTS version: ~s -> ~s\n", [erlang:system_info(version), ErtsRegex]); nomatch -> - ?ABORT("ERTS version ~s does not match required regex ~s\n", - [erlang:system_info(version), ErtsRegex]) + maybe_abort( + ShouldAbort, + "ERTS version ~s does not match required regex ~s\n", + [erlang:system_info(version), ErtsRegex]) end, OtpRegex = rebar_config:get(Config, require_otp_vsn, ".*"), @@ -61,6 +94,47 @@ ?DEBUG("Matched required OTP release: ~s -> ~s\n", [erlang:system_info(otp_release), OtpRegex]); nomatch -> - ?ABORT("OTP release ~s does not match required regex ~s\n", - [erlang:system_info(otp_release), OtpRegex]) + maybe_abort( + ShouldAbort, + "OTP release ~s does not match required regex ~s\n", + [erlang:system_info(otp_release), OtpRegex]) + end, + + case rebar_config:get(Config, require_min_otp_vsn, undefined) of + undefined -> ?DEBUG("Min OTP version unconfigured~n", []); + MinOtpVsn -> + {MinMaj, MinMin} = version_tuple(ShouldAbort, MinOtpVsn, + "configured"), + {OtpMaj, OtpMin} = version_tuple(ShouldAbort, + erlang:system_info(otp_release), + "OTP Release"), + case {OtpMaj, OtpMin} >= {MinMaj, MinMin} of + true -> + ?DEBUG("~s satisfies the requirement for vsn ~s~n", + [erlang:system_info(otp_release), + MinOtpVsn]); + false -> + maybe_abort( + ShouldAbort, + "OTP release ~s or later is required, you have: ~s~n", + [MinOtpVsn, + erlang:system_info(otp_release)]) + end + end. + +version_tuple(ShouldAbort, OtpRelease, Type) -> + case re:run(OtpRelease, "R?(\\d+)B?-?(\\d+)?", [{capture, all, list}]) of + {match, [_Full, Maj, Min]} -> + {list_to_integer(Maj), list_to_integer(Min)}; + {match, [_Full, Maj]} -> + {list_to_integer(Maj), 0}; + nomatch -> + maybe_abort(ShouldAbort, + "Cannot parse ~s version string: ~s~n", + [Type, OtpRelease]) end. + +maybe_abort(abort, Format, Data) -> + ?ABORT(Format, Data); +maybe_abort(keep_going, Format, Data) -> + ?ERROR(Format, Data). diff -Nru rebar-2.0.0/src/rebar_shell.erl rebar-2.6.0/src/rebar_shell.erl --- rebar-2.0.0/src/rebar_shell.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rebar_shell.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,91 @@ +%% -*- 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 "). + +-include("rebar.hrl"). + +-export([shell/2, info/2]). + +%% NOTE: +%% this is an attempt to replicate `erl -pa ./ebin -pa deps/*/ebin`. it is +%% mostly successful but does stop and then restart the user io system to get +%% around issues with rebar being an escript and starting in `noshell` mode. +%% it also lacks the ctrl-c interrupt handler that `erl` features. ctrl-c will +%% immediately kill the script. ctrl-g, however, works fine + +shell(_Config, _AppFile) -> + true = code:add_pathz(rebar_utils:ebin_dir()), + %% scan all processes for any with references to the old user and save them to + %% update later + NeedsUpdate = [Pid || Pid <- erlang:processes(), + proplists:get_value(group_leader, erlang:process_info(Pid)) == whereis(user) + ], + %% terminate the current user + ok = supervisor:terminate_child(kernel_sup, user), + %% start a new shell (this also starts a new user under the correct group) + _ = user_drv:start(), + %% wait until user_drv and user have been registered (max 3 seconds) + ok = wait_until_user_started(3000), + %% set any process that had a reference to the old user's group leader to the + %% new user process + _ = [erlang:group_leader(whereis(user), Pid) || Pid <- NeedsUpdate], + %% enable error_logger's tty output + ok = error_logger:swap_handler(tty), + %% disable the simple error_logger (which may have been added multiple + %% times). removes at most the error_logger added by init and the + %% error_logger added by the tty handler + ok = remove_error_handler(3), + %% this call never returns (until user quits shell) + timer:sleep(infinity). + +info(help, shell) -> + ?CONSOLE( + "Start a shell with project and deps preloaded similar to~n" + "'erl -pa ebin -pa deps/*/ebin'.~n", + [] + ). + +remove_error_handler(0) -> + ?WARN("Unable to remove simple error_logger handler~n", []); +remove_error_handler(N) -> + case gen_event:delete_handler(error_logger, error_logger, []) of + {error, module_not_found} -> ok; + {error_logger, _} -> remove_error_handler(N-1) + end. + +%% Timeout is a period to wait before giving up +wait_until_user_started(0) -> + ?ABORT("Timeout exceeded waiting for `user` to register itself~n", []), + erlang:error(timeout); +wait_until_user_started(Timeout) -> + case whereis(user) of + %% if user is not yet registered wait a tenth of a second and try again + undefined -> timer:sleep(100), wait_until_user_started(Timeout - 100); + _ -> ok + end. \ No newline at end of file diff -Nru rebar-2.0.0/src/rebar_subdirs.erl rebar-2.6.0/src/rebar_subdirs.erl --- rebar-2.0.0/src/rebar_subdirs.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_subdirs.erl 2015-06-19 16:14:28.000000000 +0000 @@ -40,7 +40,7 @@ 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 -Nru rebar-2.0.0/src/rebar_templater.erl rebar-2.6.0/src/rebar_templater.erl --- rebar-2.0.0/src/rebar_templater.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_templater.erl 2015-06-19 16:14:28.000000000 +0000 @@ -27,6 +27,7 @@ -module(rebar_templater). -export(['create-app'/2, + 'create-lib'/2, 'create-node'/2, 'list-templates'/2, create/2]). @@ -35,51 +36,109 @@ -export([resolve_variables/2, render/2]). +%% for internal use only +-export([info/2]). + -include("rebar.hrl"). --define(TEMPLATE_RE, ".*\\.template\$"). +-define(TEMPLATE_RE, "^[^._].*\\.template\$"). %% =================================================================== %% 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-lib'(Config, _File) -> + %% Alias for create w/ template=simplelib + create1(Config, "simplelib"). -'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"), - ?CONSOLE("\t* ~s: ~s (~p)\n", [BaseName, F, Type]) - 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([{Key, {list, Dicts}} | Rest], Dict) when is_list(Dicts) -> + %% just un-tag it so mustache can use it + resolve_variables(Rest, dict:store(Key, Dicts, 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), + rebar_mustache:render(Str1, Context). - TemplateId = template_id(), +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, create) -> + ?CONSOLE( + "Create skel based on template and vars.~n" + "~n" + "Valid command line options:~n" + " template= [var=foo,...]~n", []); +info(help, 'create-app') -> + ?CONSOLE( + "Create simple app skel.~n" + "~n" + "Valid command line options:~n" + " [appid=myapp]~n", []); +info(help, 'create-lib') -> + ?CONSOLE( + "Create simple lib skel.~n" + "~n" + "Valid command line options:~n" + " [libid=mylib]~n", []); +info(help, 'create-node') -> + ?CONSOLE( + "Create simple node skel.~n" + "~n" + "Valid command line options:~n" + " [nodeid=mynode]~n", []); +info(help, 'list-templates') -> + ?CONSOLE("List available templates.~n", []). + +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 @@ -89,7 +148,7 @@ %% 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 @@ -109,24 +168,24 @@ 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 -> - case file:consult(File) of - {ok, Terms} -> - %% TODO: Cleanup/merge with similar code in rebar_reltool - M = fun(_Key, _Base, Override) -> Override end, - dict:merge(M, Context0, dict:from_list(Terms)); + case consult(load_file([], file, File)) of {error, Reason} -> ?ABORT("Unable to load template_vars from ~s: ~p\n", - [File, Reason]) + [File, Reason]); + Terms -> + %% TODO: Cleanup/merge with similar code in rebar_reltool + M = fun(_Key, _Base, Override) -> Override end, + dict:merge(M, Context0, dict:from_list(Terms)) end end, %% 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 @@ -138,80 +197,63 @@ %% 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, []). +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), -%% -%% 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). + %% 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_disk_templates() -> - OtherTemplates = find_other_templates(), - HomeFiles = rebar_utils:find_files(filename:join([os:getenv("HOME"), - ".rebar", "templates"]), - ?TEMPLATE_RE), - LocalFiles = rebar_utils:find_files(".", ?TEMPLATE_RE), +find_escript_templates(Files) -> + [{escript, Name} + || {Name, _Bin} <- Files, + re:run(Name, ?TEMPLATE_RE, [{capture, none}]) == match]. + +find_disk_templates(Config) -> + OtherTemplates = find_other_templates(Config), + HomeTemplates = filename:join([os:getenv("HOME"), ".rebar", "templates"]), + HomeFiles = rebar_utils:find_files_by_ext(HomeTemplates, ".template"), + Recursive = rebar_config:is_recursive(Config), + LocalFiles = rebar_utils:find_files_by_ext(".", ".template", Recursive), [{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 -> - rebar_utils:find_files(TemplateDir, ?TEMPLATE_RE) + rebar_utils:find_files_by_ext(TemplateDir, ".template") end. select_template([], Template) -> @@ -227,10 +269,10 @@ %% %% 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. @@ -250,15 +292,15 @@ %% 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, []); @@ -271,7 +313,7 @@ case Result of {ok, Tokens, _} -> {ok, Term} = erl_parse:parse_term(Tokens), - consult([], Remaining, [Term | Acc]); + consult([], Remaining, [maybe_dict(Term) | Acc]); {eof, _Other} -> lists:reverse(Acc); {error, Info, _} -> @@ -282,6 +324,13 @@ end. +maybe_dict({Key, {list, Dicts}}) -> + %% this is a 'list' element; a list of lists representing dicts + {Key, {list, [dict:from_list(D) || D <- Dicts]}}; +maybe_dict(Term) -> + Term. + + write_file(Output, Data, Force) -> %% determine if the target file already exists FileExists = filelib:is_regular(Output), @@ -309,93 +358,128 @@ {error, exists} end. +prepend_instructions(Instructions, Rest) when is_list(Instructions) -> + Instructions ++ Rest; +prepend_instructions(Instruction, Rest) -> + [Instruction|Rest]. %% %% 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, [{'if', Cond, True} | Rest], TemplateType, + TemplateName, Context, Force, ExistingFiles) -> + execute_template(Files, [{'if', Cond, True, []}|Rest], TemplateType, + TemplateName, Context, Force, ExistingFiles); +execute_template(Files, [{'if', Cond, True, False} | Rest], TemplateType, + TemplateName, Context, Force, ExistingFiles) -> + Instructions = case dict:find(Cond, Context) of + {ok, true} -> + True; + {ok, "true"} -> + True; + _ -> + False + end, + execute_template(Files, prepend_instructions(Instructions, Rest), + TemplateType, TemplateName, Context, Force, + ExistingFiles); +execute_template(Files, [{'case', Variable, Values, Instructions} | Rest], TemplateType, + TemplateName, Context, Force, ExistingFiles) -> + {ok, Value} = dict:find(Variable, Context), + Instructions2 = case lists:member(Value, Values) of + true -> + Instructions; + _ -> + [] + end, + execute_template(Files, prepend_instructions(Instructions2, Rest), + TemplateType, TemplateName, Context, Force, + ExistingFiles); +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 -Nru rebar-2.0.0/src/rebar_upgrade.erl rebar-2.6.0/src/rebar_upgrade.erl --- rebar-2.0.0/src/rebar_upgrade.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_upgrade.erl 2015-06-19 16:14:28.000000000 +0000 @@ -32,24 +32,30 @@ -export(['generate-upgrade'/2]). +%% for internal use only +-export([info/2]). + -define(TMP, "_tmp"). %% ==================================================================== %% 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, OldVer} = run_checks(Config, OldVerPath, + ReltoolConfig), NameVer = NewName ++ "_" ++ NewVer, + OldRelName = get_old_rel_name(OldVerPath, OldVer, NewName), %% Save the code path prior to doing anything OrigPath = code:get_path(), @@ -58,7 +64,7 @@ ok = setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer), %% Build the package - run_systools(NameVer, NewName), + run_systools(NameVer, OldRelName), %% Boot file changes {ok, _} = boot_files(TargetDir, NewVer, NewName), @@ -70,24 +76,30 @@ ok = cleanup(NameVer), %% Restore original path - true = code:set_path(OrigPath), + true = rebar_utils:cleanup_code_path(OrigPath), - ok. + {ok, Config}. %% =================================================================== %% Internal functions %% ================================================================== -run_checks(OldVerPath, ReltoolConfig) -> +info(help, 'generate-upgrade') -> + ?CONSOLE("Build an upgrade package.~n" + "~n" + "Valid command line options:~n" + " previous_release=path~n" + " target_dir=target_dir (optional)~n", + []). + +run_checks(Config, OldVerPath, ReltoolConfig) -> true = rebar_utils:prop_check(filelib:is_dir(OldVerPath), "Release directory doesn't exist (~p)~n", [OldVerPath]), {Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolConfig), - NewVerPath = - filename:join([rebar_rel_utils:get_target_parent_dir(ReltoolConfig), - Name]), + NewVerPath = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), true = rebar_utils:prop_check(filelib:is_dir(NewVerPath), "Release directory doesn't exist (~p)~n", [NewVerPath]), @@ -111,7 +123,7 @@ rebar_utils:prop_check(Ver == NewVer, "Reltool and .rel versions do not match~n", []), - {NewVerPath, NewName, NewVer}. + {NewVerPath, NewName, NewVer, OldVer}. setup(OldVerPath, NewVerPath, NewName, NewVer, NameVer) -> Src = filename:join([NewVerPath, "releases", @@ -128,9 +140,9 @@ "lib", "*", "ebin"])) ])). -run_systools(NewVer, Name) -> +run_systools(NewVer, OldRelName) -> Opts = [silent], - NameList = [Name], + NameList = [OldRelName], case systools:make_relup(NewVer, NameList, NameList, Opts) of {error, _, Msg} -> ?ABORT("Systools [systools:make_relup/4] aborted with: ~p~n", @@ -171,13 +183,24 @@ filename:join([TargetDir, "releases", Ver, "start_clean.boot"]), filename:join([".", ?TMP, "releases", Ver, "start_clean.boot"])), - {ok, _} = file:copy( - filename:join([TargetDir, "releases", Ver, "sys.config"]), - filename:join([".", ?TMP, "releases", Ver, "sys.config"])), - - {ok, _} = file:copy( - filename:join([TargetDir, "releases", Ver, "vm.args"]), - filename:join([".", ?TMP, "releases", Ver, "vm.args"])). + SysConfig = filename:join([TargetDir, "releases", Ver, "sys.config"]), + _ = case filelib:is_regular(SysConfig) of + true -> + {ok, _} = file:copy( + SysConfig, + filename:join([".", ?TMP, "releases", Ver, + "sys.config"])); + false -> ok + end, + + VmArgs = filename:join([TargetDir, "releases", Ver, "vm.args"]), + case filelib:is_regular(VmArgs) of + true -> + {ok, _} = file:copy( + VmArgs, + filename:join([".", ?TMP, "releases", Ver, "vm.args"])); + false -> {ok, 0} + end. make_tar(NameVer, NewVer, NewName) -> Filename = NameVer ++ ".tar.gz", @@ -242,3 +265,7 @@ Error -> Error end. + +get_old_rel_name(OldVerPath, OldVer, Name) -> + OldRelFile = rebar_rel_utils:get_rel_file_path(Name, OldVerPath, OldVer), + filename:basename(OldRelFile, ".rel"). diff -Nru rebar-2.0.0/src/rebar_utils.erl rebar-2.6.0/src/rebar_utils.erl --- rebar-2.0.0/src/rebar_utils.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_utils.erl 2015-06-19 16:14:28.000000000 +0000 @@ -31,24 +31,48 @@ get_arch/0, wordsize/0, sh/2, + sh_send/3, find_files/2, find_files/3, + find_files_by_ext/2, + find_files_by_ext/3, now_str/0, ensure_dir/1, - beam_to_mod/2, beams/1, + beam_to_mod/2, + beams/1, erl_to_mod/1, + abort/0, abort/2, escript_foldl/3, 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_list/4, get_deprecated_list/5, - get_deprecated_local/4, get_deprecated_local/5, - delayed_halt/1]). + 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, + erl_opts/1, + src_dirs/1, + ebin_dir/0, + base_dir/1, + processing_base_dir/1, + processing_base_dir/2, + patch_env/2, + cleanup_code_path/1]). + +%% for internal use only +-export([otp_release/0]). + +-dialyzer({no_missing_calls, escript_foldl/3}). -include("rebar.hrl"). @@ -70,7 +94,7 @@ get_arch() -> Words = wordsize(), - erlang:system_info(otp_release) ++ "-" + otp_release() ++ "-" ++ erlang:system_info(system_architecture) ++ "-" ++ Words. wordsize() -> @@ -82,6 +106,25 @@ integer_to_list(8 * erlang:system_info(wordsize)) end. +sh_send(Command0, String, Options0) -> + ?INFO("sh_send info:\n\tcwd: ~p\n\tcmd: ~s < ~s\n", + [get_cwd(), Command0, String]), + ?DEBUG("\topts: ~p\n", [Options0]), + + DefaultOptions = [use_stdout, abort_on_error], + Options = [expand_sh_flag(V) + || V <- proplists:compact(Options0 ++ DefaultOptions)], + + Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])), + PortSettings = proplists:get_all_values(port_settings, Options) ++ + [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + Port = open_port({spawn, Command}, PortSettings), + + %% allow us to send some data to the shell command's STDIN + %% Erlang doesn't let us get any reply after sending an EOF, though... + Port ! {self(), {command, String}}, + port_close(Port). + %% %% Options = [Option] -- defaults to [use_stdout, abort_on_error] %% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env} @@ -104,6 +147,7 @@ Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])), PortSettings = proplists:get_all_values(port_settings, Options) ++ [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + ?DEBUG("Port Cmd: ~p\nPort Opts: ~p\n", [Command, PortSettings]), Port = open_port({spawn, Command}, PortSettings), case sh_loop(Port, OutputHandler, []) of @@ -120,6 +164,28 @@ filelib:fold_files(Dir, Regex, Recursive, fun(F, Acc) -> [F | Acc] end, []). +%% Find files by extension, for example ".erl", avoiding resource fork +%% files in OS X. Such files are named for example src/._xyz.erl +%% Such files may also appear with network filesystems on OS X. +%% +%% The Ext is really a regexp, with any leading dot implicitly +%% escaped, and anchored at the end of the string. +%% +find_files_by_ext(Dir, Ext) -> + find_files_by_ext(Dir, Ext, true). + +find_files_by_ext(Dir, Ext, Recursive) -> + %% Convert simple extension to proper regex + EscapeDot = case Ext of + "." ++ _ -> + "\\"; + _ -> + %% allow for other suffixes, such as _pb.erl + "" + end, + ExtRe = "^[^._].*" ++ EscapeDot ++ Ext ++ [$$], + find_files(Dir, ExtRe, Recursive). + now_str() -> {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), lists:flatten(io_lib:format("~4b/~2..0b/~2..0b ~2..0b:~2..0b:~2..0b", @@ -137,10 +203,14 @@ Error end. +-spec abort() -> no_return(). +abort() -> + throw(rebar_abort). + -spec abort(string(), [term()]) -> no_return(). abort(String, Args) -> ?ERROR(String, Args), - delayed_halt(1). + abort(). %% TODO: Rename emulate_escript_foldl to escript_foldl and remove %% this function when the time is right. escript:foldl/3 was an @@ -167,9 +237,11 @@ %% Convert all the entries in the code path to absolute paths. expand_code_path() -> - CodePath = lists:foldl(fun (Path, Acc) -> - [filename:absname(Path) | Acc] - end, [], code:get_path()), + CodePath = lists:foldl( + fun(Path, Acc) -> + Path1 = rmemo:call(filename, absname, [Path]), + [Path1 | Acc] + end, [], code:get_path()), code:set_path(lists:reverse(CodePath)). %% @@ -183,74 +255,39 @@ %% No variables to expand InStr; _ -> - VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", [global]), + ReOpts = [global, unicode, {return, list}], + VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", ReOpts), %% Use a regex to match/replace: - %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO} - RegEx = io_lib:format("\\\$(~s(\\s|$)|{~s})", [VarName, VarName]), - ReOpts = [global, {return, list}], + %% Given variable "FOO", match $FOO\W | $FOOeol | ${FOO}. + RegEx = io_lib:format("\\\$(~s(\\W|$)|{~s})", [VarName, VarName]), re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts) end. -vcs_vsn(Vcs, Dir) -> - Key = {Vcs, Dir}, - try ets:lookup_element(rebar_vsn_cache, Key, 2) - catch - error:badarg -> - VsnString = vcs_vsn_1(Vcs, Dir), - ets:insert(rebar_vsn_cache, {Key, VsnString}), - VsnString +vcs_vsn(Config, Vsn, Dir) -> + Key = {Vsn, Dir}, + Cache = rebar_config:get_xconf(Config, vsn_cache), + case dict:find(Key, Cache) of + error -> + VsnString = vcs_vsn_1(Vsn, Dir), + 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 - undefined -> - case rebar_config:get_global(OldOpt, Default) of - undefined -> - undefined; - 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). @@ -310,10 +347,126 @@ 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"). + +base_dir(Config) -> + rebar_config:get_xconf(Config, base_dir). + +processing_base_dir(Config) -> + Cwd = rebar_utils:get_cwd(), + processing_base_dir(Config, Cwd). + +processing_base_dir(Config, Dir) -> + AbsDir = filename:absname(Dir), + AbsDir =:= base_dir(Config). + +%% @doc Returns the list of environment variables including 'REBAR' which +%% points to the rebar executable used to execute the currently running +%% command. The environment is not modified if rebar was invoked +%% programmatically. +-spec patch_env(rebar_config:config(), [{string(), string()}]) + -> [{string(), string()}]. +patch_env(Config, []) -> + %% If we reached an empty list, the env did not contain the REBAR variable. + case rebar_config:get_xconf(Config, escript, "") of + "" -> % rebar was invoked programmatically + []; + Path -> + [{"REBAR", Path}] + end; +patch_env(_Config, [{"REBAR", _} | _]=All) -> + All; +patch_env(Config, [E | Rest]) -> + [E | patch_env(Config, Rest)]. + %% ==================================================================== %% Internal functions %% ==================================================================== +otp_release() -> + rmemo:call(fun otp_release_1/1, [(erlang:system_info(otp_release))]). + +%% If OTP <= R16, otp_release is already what we want. +otp_release_1([$R,N|_]=Rel) when is_integer(N) -> + Rel; +%% If OTP >= 17.x, erlang:system_info(otp_release) returns just the +%% major version number, we have to read the full version from +%% a file. See http://www.erlang.org/doc/system_principles/versions.html +otp_release_1(Rel) -> + Files = [ + filename:join([code:root_dir(), "releases", Rel, "OTP_VERSION"]), + filename:join([code:root_dir(), "OTP_VERSION"]) + ], + + %% It's possible that none of the above files exist on the filesystem, in + %% which case, we're just going to rely on the provided "Rel" (which should + %% just be the value of `erlang:system_info(otp_release)`). + case read_otp_version_files(Files) of + undefined -> + warn_missing_otp_version_file(Rel), + Rel; + Vsn -> + Vsn + end. + +warn_missing_otp_version_file(Rel) -> + ?WARN("No OTP_VERSION file found. Using version string ~p.~n", [Rel]). + +%% Try to open each file path provided, and if any of them exist on the +%% filesystem, read their contents and return the value of the first one found. +read_otp_version_files([]) -> + undefined; +read_otp_version_files([File | Rest]) -> + case file:read_file(File) of + {ok, Vsn} -> normalize_otp_version(Vsn); + {error, enoent} -> read_otp_version_files(Rest) + end. + +%% Takes the Version binary as read from the OTP_VERSION file and strips any +%% trailing "**" and trailing "\n", returning the string as a list. +normalize_otp_version(Vsn) -> + %% It's fine to rely on the binary module here because we can + %% be sure that it's available when the otp_release string does + %% not begin with $R. + Size = byte_size(Vsn), + %% The shortest vsn string consists of at least two digits + %% followed by "\n". Therefore, it's safe to assume Size >= 3. + case binary:part(Vsn, {Size, -3}) of + <<"**\n">> -> + %% The OTP documentation mentions that a system patched + %% using the otp_patch_apply tool available to licensed + %% customers will leave a '**' suffix in the version as a + %% flag saying the system consists of application versions + %% from multiple OTP versions. We ignore this flag and + %% drop the suffix, given for all intents and purposes, we + %% cannot obtain relevant information from it as far as + %% tooling is concerned. + binary:bin_to_list(Vsn, {0, Size - 3}); + _ -> + binary:bin_to_list(Vsn, {0, Size - 1}) + end. + get_deprecated_3(Get, Config, OldOpt, NewOpt, Default, When) -> case Get(Config, NewOpt, Default) of Default -> @@ -328,15 +481,28 @@ 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) -> case os:type() of {win32,nt} -> - "cmd /q /c " + Cmd1 = "cmd /q /c " ++ lists:foldl(fun({Key, Value}, Acc) -> expand_env_variable(Acc, Key, Value) - end, Cmd, Env); + end, Cmd, Env), + %% Remove left-over vars + re:replace(Cmd1, "\\\$\\w+|\\\${\\w+}", "", + [global, {return, list}]); _ -> Cmd end. @@ -377,8 +543,9 @@ -spec log_and_abort(string(), {integer(), string()}) -> no_return(). log_and_abort(Command, {Rc, Output}) -> - ?ABORT("~s failed with error: ~w and output:~n~s~n", - [Command, Rc, Output]). + ?ABORT("sh(~s)~n" + "failed with return code ~w and the following output:~n" + "~s~n", [Command, Rc, Output]). sh_loop(Port, Fun, Acc) -> receive @@ -422,23 +589,87 @@ 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 + {plain, VsnString} -> + VsnString; + {cmd, CmdString} -> + vcs_vsn_invoke(CmdString, Dir); + unknown -> + ?ABORT("vcs_vsn: Unknown vsn format: ~p\n", [Vcs]); + 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(p4) -> "echo #head"; +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_cmd(Version) when is_list(Version) -> {plain, Version}; +vcs_vsn_cmd(_) -> unknown. 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]). + +cleanup_code_path(OrigPath) -> + CurrentPath = code:get_path(), + AddedPaths = CurrentPath -- OrigPath, + %% If someone has removed paths, it's hard to get them back into + %% the right order, but since this is currently rare, we can just + %% fall back to code:set_path/1. + case CurrentPath -- AddedPaths of + OrigPath -> + _ = [code:del_path(Path) || Path <- AddedPaths], + true; + _ -> + code:set_path(OrigPath) + end. diff -Nru rebar-2.0.0/src/rebar_xref.erl rebar-2.6.0/src/rebar_xref.erl --- rebar-2.0.0/src/rebar_xref.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/src/rebar_xref.erl 2015-06-19 16:14:28.000000000 +0000 @@ -28,7 +28,8 @@ %% ------------------------------------------------------------------- %% This module borrows heavily from http://github.com/etnt/exrefcheck project as -%% written by Torbjorn Tornkvist , Daniel Luna and others. +%% written by Torbjorn Tornkvist , Daniel Luna +%% and others. %% ------------------------------------------------------------------- -module(rebar_xref). @@ -36,6 +37,9 @@ -export([xref/2]). +%% for internal use only +-export([info/2]). + %% =================================================================== %% Public API %% =================================================================== @@ -43,54 +47,52 @@ xref(Config, _) -> %% Spin up xref {ok, _} = xref:start(xref), - ok = xref:set_library_path(xref, code_path()), + ok = xref:set_library_path(xref, code_path(Config)), xref:set_default(xref, [{warnings, rebar_config:get(Config, xref_warnings, false)}, - {verbose, rebar_config:is_verbose()}]), + {verbose, rebar_log: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, - [exports_not_used, - undefined_function_calls]), - - %% Look for exports that are unused by anything - ExportsNoWarn = - case lists:member(exports_not_used, XrefChecks) of - true -> - check_exports_not_used(); - false -> - true - end, + ConfXrefChecks = rebar_config:get(Config, xref_checks, + [exports_not_used, + undefined_function_calls]), + + SupportedXrefs = [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions], + + XrefChecks = sets:to_list(sets:intersection( + sets:from_list(SupportedXrefs), + sets:from_list(ConfXrefChecks))), - %% Look for calls to undefined functions - UndefNoWarn = - case lists:member(undefined_function_calls, XrefChecks) of - true -> - check_undefined_function_calls(); - false -> - true - end, + %% Run xref checks + XrefNoWarn = xref_checks(XrefChecks), %% Run custom queries QueryChecks = rebar_config:get(Config, xref_queries, []), QueryNoWarn = lists:all(fun check_query/1, QueryChecks), %% Restore the original code path - true = code:set_path(OrigPath), + true = rebar_utils:cleanup_code_path(OrigPath), %% Stop xref stopped = xref:stop(xref), - case lists:member(false, [ExportsNoWarn, UndefNoWarn, QueryNoWarn]) of + case lists:member(false, [XrefNoWarn, QueryNoWarn]) of true -> - ?FAIL; + case rebar_config:get_xconf(Config, keep_going, false) of + false -> + ?FAIL; + true -> + ok + end; false -> ok end. @@ -99,26 +101,38 @@ %% Internal functions %% =================================================================== -check_exports_not_used() -> - {ok, UnusedExports0} = xref:analyze(xref, exports_not_used), - UnusedExports = filter_away_ignored(UnusedExports0), - - %% Report all the unused functions - display_mfas(UnusedExports, "is unused export (Xref)"), - UnusedExports =:= []. - -check_undefined_function_calls() -> - {ok, UndefinedCalls0} = xref:analyze(xref, undefined_function_calls), - UndefinedCalls = - [{find_mfa_source(Caller), format_fa(Caller), format_mfa(Target)} - || {Caller, Target} <- UndefinedCalls0], - - lists:foreach( - fun({{Source, Line}, FunStr, Target}) -> - ?CONSOLE("~s:~w: Warning ~s calls undefined function ~s\n", - [Source, Line, FunStr, Target]) - end, UndefinedCalls), - UndefinedCalls =:= []. +info(help, xref) -> + ?CONSOLE( + "Run cross reference analysis.~n" + "~n" + "Valid rebar.config options:~n" + " ~p~n" + " ~p~n" + " ~p~n" + " ~p~n", + [ + {xref_warnings, false}, + {xref_extra_paths,[]}, + {xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions]}, + {xref_queries, + [{"(xc - uc) || (xu - x - b" + " - (\"mod\":\".*foo\"/\"4\"))",[]}]} + ]). + +xref_checks(XrefChecks) -> + XrefWarnCount = lists:foldl(fun run_xref_check/2, 0, XrefChecks), + XrefWarnCount =:= 0. + +run_xref_check(XrefCheck, Acc) -> + {ok, Results} = xref:analyze(xref, XrefCheck), + FilteredResults =filter_xref_results(XrefCheck, Results), + lists:foreach(fun(Res) -> + display_xref_result(XrefCheck, Res) + end, + FilteredResults), + Acc + length(FilteredResults). check_query({Query, Value}) -> {ok, Answer} = xref:q(xref, Query), @@ -131,54 +145,115 @@ true end. -code_path() -> - [P || P <- code:get_path(), - filelib:is_dir(P)] ++ [filename:join(rebar_utils:get_cwd(), "ebin")]. +code_path(Config) -> + %% Slight hack to ensure that sub_dirs get properly included + %% in code path for xref -- otherwise one gets a lot of undefined + %% functions, even though those functions are present as part + %% of compilation. H/t to @dluna. Long term we should tie more + %% properly into the overall compile code path if possible. + BaseDir = rebar_utils:base_dir(Config), + [P || P <- code:get_path() ++ + rebar_config:get(Config, xref_extra_paths, []) ++ + [filename:join(BaseDir, filename:join(SubDir, "ebin")) + || SubDir <- rebar_config:get(Config, sub_dirs, [])], + filelib:is_dir(P)]. %% %% Ignore behaviour functions, and explicitly marked functions %% -filter_away_ignored(UnusedExports) -> - %% Functions can be ignored by using - %% -ignore_xref([{F, A}, ...]). - - %% Setup a filter function that builds a list of behaviour callbacks and/or - %% 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)], - [{Mod, F, A} || {F, A} <- Ignore ++ lists:flatten(Callbacks)] +%% Functions can be ignored by using +%% -ignore_xref([{F, A}, {M, F, A}...]). + +get_xref_ignorelist(Mod, XrefCheck) -> + %% Get ignore_xref attribute and combine them in one list + Attributes = + try + Mod:module_info(attributes) + catch + _Class:_Error -> [] end, - AttrIgnore = - lists:flatten( - lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))), - [X || X <- UnusedExports, not lists:member(X, AttrIgnore)]. + IgnoreXref = keyall(ignore_xref, Attributes), -kf(Key, List) -> - case lists:keyfind(Key, 1, List) of - {Key, Value} -> - Value; - false -> - [] - end. + BehaviourCallbacks = get_behaviour_callbacks(XrefCheck, Attributes), -display_mfas([], _Message) -> - ok; -display_mfas([{_Mod, Fun, Args} = MFA | Rest], Message) -> - {Source, Line} = find_mfa_source(MFA), - ?CONSOLE("~s:~w: Warning: function ~s/~w ~s\n", - [Source, Line, Fun, Args, Message]), - display_mfas(Rest, Message). + %% And create a flat {M,F,A} list + lists:foldl( + fun({F, A}, Acc) -> [{Mod,F,A} | Acc]; + ({M, F, A}, Acc) -> [{M,F,A} | Acc] + end, [], lists:flatten([IgnoreXref, BehaviourCallbacks])). + +keyall(Key, List) -> + lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List). + +get_behaviour_callbacks(exports_not_used, Attributes) -> + [B:behaviour_info(callbacks) || + B <- keyall(behaviour, Attributes) ++ keyall(behavior, Attributes)]; +get_behaviour_callbacks(_XrefCheck, _Attributes) -> + []. + +parse_xref_result({_, MFAt}) -> MFAt; +parse_xref_result(MFAt) -> MFAt. + +filter_xref_results(XrefCheck, XrefResults) -> + SearchModules = lists:usort( + lists:map( + fun({Mt,_Ft,_At}) -> Mt; + ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms; + (_) -> undefined + end, XrefResults)), + + Ignores = lists:flatmap(fun(Module) -> + get_xref_ignorelist(Module, XrefCheck) + end, SearchModules), + + [Result || Result <- XrefResults, + not lists:member(parse_xref_result(Result), Ignores)]. + +display_xref_result(Type, XrefResult) -> + { Source, SMFA, TMFA } = case XrefResult of + {MFASource, MFATarget} -> + {format_mfa_source(MFASource), + format_mfa(MFASource), + format_mfa(MFATarget)}; + MFATarget -> + {format_mfa_source(MFATarget), + format_mfa(MFATarget), + undefined} + end, + case Type of + undefined_function_calls -> + ?CONSOLE("~sWarning: ~s calls undefined function ~s (Xref)\n", + [Source, SMFA, TMFA]); + undefined_functions -> + ?CONSOLE("~sWarning: ~s is undefined function (Xref)\n", + [Source, SMFA]); + locals_not_used -> + ?CONSOLE("~sWarning: ~s is unused local function (Xref)\n", + [Source, SMFA]); + exports_not_used -> + ?CONSOLE("~sWarning: ~s is unused export (Xref)\n", + [Source, SMFA]); + deprecated_function_calls -> + ?CONSOLE("~sWarning: ~s calls deprecated function ~s (Xref)\n", + [Source, SMFA, TMFA]); + deprecated_functions -> + ?CONSOLE("~sWarning: ~s is deprecated function (Xref)\n", + [Source, SMFA]); + Other -> + ?CONSOLE("~sWarning: ~s - ~s xref check: ~s (Xref)\n", + [Source, SMFA, TMFA, Other]) + end. format_mfa({M, F, A}) -> ?FMT("~s:~s/~w", [M, F, A]). -format_fa({_M, F, A}) -> - ?FMT("~s/~w", [F, A]). +format_mfa_source(MFA) -> + case find_mfa_source(MFA) of + {module_not_found, function_not_found} -> ""; + {Source, function_not_found} -> ?FMT("~s: ", [Source]); + {Source, Line} -> ?FMT("~s:~w: ", [Source, Line]) + end. %% %% Extract an element from a tuple, or undefined if N > tuple size @@ -191,14 +266,18 @@ Value end. - %% %% Given a MFA, find the file and LOC where it's defined. Note that %% xref doesn't work if there is no abstract_code, so we can avoid %% being too paranoid here. %% find_mfa_source({M, F, A}) -> - {M, Bin, _} = code:get_object_code(M), + case code:get_object_code(M) of + error -> {module_not_found, function_not_found}; + {M, Bin, _} -> find_function_source(M,F,A,Bin) + end. + +find_function_source(M, F, A, Bin) -> AbstractCode = beam_lib:chunks(Bin, [abstract_code]), {ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode, %% Extract the original source filename from the abstract code diff -Nru rebar-2.0.0/src/rmemo.erl rebar-2.6.0/src/rmemo.erl --- rebar-2.0.0/src/rmemo.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/src/rmemo.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,294 @@ +%%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%%% ex: ft=erlang ts=4 sw=4 et +%%% +%%%------------------------------------------------------------------- +%%% @author Tuncer Ayaz +%%% @copyright 2015, Tuncer Ayaz +%%% @doc +%%% memoization server +%%% @end +%%%------------------------------------------------------------------- +%%% +%%% Copyright (c) 2015 Tuncer Ayaz +%%% +%%% Permission to use, copy, modify, and/or distribute this software +%%% for any purpose with or without fee is hereby granted, provided +%%% that the above copyright notice and this permission notice appear +%%% in all copies. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +%%% WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +%%% WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +%%% AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +%%% CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +%%% LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +%%% NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +%%% CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +%% rebar-specific modifications: +%% 1. rename to rmemo.erl +%% 2. add support for R13 (see ets_tab/0) + +-module(rmemo). + +-behaviour(gen_server). + +%% API +-export( + [ + start/0, + start_link/0, + stop/0, + call/2, + call/3 + ]). + +%% gen_server callbacks +-export( + [ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +-define(SERVER, ?MODULE). +-define(TABLE, ?MODULE). + +-record(state, + { + ets_tab :: ets:tab() + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + + +%%-------------------------------------------------------------------- +%% @doc +%% Start the server +%% @end +%%-------------------------------------------------------------------- +-type reason() :: term(). +-type error() :: {error, reason()}. +-type start_res() :: {ok, pid()} | 'ignore' | error(). +-spec start() -> start_res(). +start() -> + gen_server:start({local, ?SERVER}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- +%% @doc +%% Start the server +%% @end +%%-------------------------------------------------------------------- +-type start_link_res() :: start_res(). +-spec start_link() -> start_link_res(). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- +%% @doc +%% Stop the server +%% @end +%%-------------------------------------------------------------------- +stop() -> + gen_server:cast(?SERVER, stop). + +%%-------------------------------------------------------------------- +%% @doc +%% Call function and memoize result +%% +%% Instead of +%% +%% Res = Fun(A1, A2, [List1]) +%% +%% you call +%% +%% Res = memo:call(Fun, [A1, A2, [List1]]) +%% +%% or instead of +%% +%% +%% Res = mod:expensive_function(A1, A2, [List1]) +%% +%% +%% you call +%% +%% +%% Res = memo:call(fun mod:expensive_function/3, [A1, A2, [List1]]) +%% +%% +%% and any subsequent call will fetch the cached result and avoid the +%% computation. +%% +%% This is of course only useful for expensive computations that are +%% known to produce the same result given same arguments. It's worth +%% mentioning that your call should be side-effect free, as naturally +%% those won't be replayed. +%% +%% @end +%%-------------------------------------------------------------------- +-type fun_args() :: list(). +-spec call(fun(), fun_args()) -> term(). +call(F, A) -> + call_1({F, A}). + +%%-------------------------------------------------------------------- +%% @doc +%% Call function and memoize result +%% +%% Instead of +%% +%% Res = mod:expensive_function(A1, A2, [List1]) +%% +%% you call +%% +%% Res = memo:call(mod, expensive_function, [A1, A2, [List1]]) +%% +%% and any subsequent call will fetch the cached result and avoid the +%% computation. +%% +%% This is of course only useful for expensive computations that are +%% known to produce the same result given same arguments. It's worth +%% mentioning that your call should be side-effect free, as naturally +%% those won't be replayed.%% +%% +%% @end +%%-------------------------------------------------------------------- +%% fun() is not just the name of a fun, so we define an alias for +%% atom() for call(M, F, A). +-type fun_name() :: atom(). +-spec call(module(), fun_name(), fun_args()) -> term(). +call(M, F, A) when is_list(A) -> + call_1({M, F, A}). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initialize the server +%% @end +%%-------------------------------------------------------------------- +init(_) -> + {ok, + #state{ + ets_tab = ets_tab() + } + }. + +-spec ets_tab() -> ets:tab(). +ets_tab() -> + ErtsApp = filename:join(code:lib_dir(erts, ebin), "erts.app"), + Concurrency = + %% If erts.app exists, we run on at least R14. That means we + %% can use ets read_concurrency. + %% TODO: Remove and revert to vanilla memo.erl from + %% https://github.com/tuncer/memo once we require at least + %% R14B and drop support for R13. + case filelib:is_regular(ErtsApp) of + true -> + [{read_concurrency, true}]; + false -> + [] + end, + ets:new( + ?TABLE, + [ + named_table, + protected, + set + ] + ++ Concurrency + ). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handle call messages +%% @end +%%-------------------------------------------------------------------- +handle_call({save, Key, Res}, _From, State) -> + {reply, save(Key, Res), State}; +handle_call(_Request, _From, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handle cast messages +%% @end +%%-------------------------------------------------------------------- +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handle all non call/cast messages +%% @end +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +-type call() :: {module(), fun_name(), fun_args()} | {fun(), fun_args()}. +-spec call_1(call()) -> term(). +call_1(Call) -> + Key = key(Call), + case ets:lookup(?TABLE, Key) of + [] -> + Res = apply(Call), + true = gen_server:call(?SERVER, {save, Key, Res}, infinity), + Res; + [{Key, Mem}] -> + Mem + end. + +-type key_args() :: call(). +-type key() :: non_neg_integer(). +-spec key(key_args()) -> key(). +key(Call) -> + erlang:phash2(Call). + +-spec apply(call()) -> term(). +apply({F, A}) -> + erlang:apply(F, A); +apply({M, F, A}) -> + erlang:apply(M, F, A). + +-type val() :: term(). +-spec save(key(), val()) -> true. +save(K, V) -> + ets:insert(?TABLE, {K, V}). diff -Nru rebar-2.0.0/test/rebar_compiler_tests.erl rebar-2.6.0/test/rebar_compiler_tests.erl --- rebar-2.0.0/test/rebar_compiler_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/test/rebar_compiler_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,192 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Vlad Dumitrescu (vladdu55@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +%% @author Vlad Dumitrescu +%% @author Chris Bernard +%% @doc This tests functionality provided by the rebar command 'compile'. +%% ------------------------------------------------------------------- +-module(rebar_compiler_tests). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +%% Assuming this test is run inside the rebar 'eunit' +%% command, the current working directory will be '.eunit' +-define(REBAR_SCRIPT, "../rebar"). + +-define(TMP_DIR, "tmp_eunit/"). + +%% ==================================================================== +%% Rebar compiler tests +%% ==================================================================== + +keep_going_test_() -> + {"Ensure compiler keeps going after finding errors, when --keep-going is requested", + setup, + fun() -> + setup_basic_project(), + rebar("-k compile") + end, + fun teardown/1, + fun(RebarOut)-> + [ + {"Continue after error", + ?_assert(string:str(RebarOut, "Continuing after build error") =/= 0)}, + ?_assert(string:str(RebarOut, "ERROR: compile failed") == 0) + ] ++ assert_files_in("ebin", ["ebin/myapp_mymod2.beam"]) + end + }. + +keep_going_firstfiles_test_() -> + {"Ensure compiler keeps going after finding errors in erl_first_files, when --keep-going is requested", + setup, + fun() -> + setup_basic_project(), + setup_rebar_config(), + rebar("-k compile") + end, + fun teardown/1, + fun(RebarOut)-> + [ + {"Continue after error", + ?_assert(string:str(RebarOut, "Continuing after build error") =/= 0)}, + ?_assert(string:str(RebarOut, "ERROR: compile failed") == 0) + ] ++ assert_files_in("ebin", ["ebin/myapp_mymod2.beam"]) + end + }. + +not_keep_going_test_() -> + {"Ensure compiler stops after finding errors", + setup, + fun() -> + setup_basic_project(), + setup_rebar_config(), + rebar("compile") + end, + fun teardown/1, + fun(RebarOut)-> + [ + {"Exit after error", + ?_assert(string:str(RebarOut, "ERROR: compile failed") =/= 0)} + ] ++ assert_files_not_in("ebin", ["ebin/myapp_mymod2.beam"]) + end + }. + +%% ==================================================================== +%% Setup and Teardown +%% ==================================================================== + +-define(rebar_config, + ["{erl_first_files, [\"src/myapp_mymod1.erl\"]}." + ]). + +%% this file has a syntax error +-define(myapp_mymod1, + ["-module(myapp_mymod1).\n", + "-export([myfunc/0]).\n", + "my func() -> [4], ok.\n"]). + +-define(myapp_mymod2, + ["-module(myapp_mymod2).\n", + "-export([myfunc/0]).\n", + "myfunc() -> [4], ok.\n"]). + + +make_tmp_dir() -> + case file:make_dir(?TMP_DIR) of + ok -> + ok; + {error, eexist} -> + remove_tmp_dir(), + make_tmp_dir(); + Error -> + throw(Error) + end. + +setup_environment() -> + ok = make_tmp_dir(), + prepare_rebar_script(), + ok = file:set_cwd(?TMP_DIR). + +setup_rebar_config() -> + ok = file:write_file("rebar.config", ?rebar_config). + +setup_basic_project() -> + setup_environment(), + rebar("create-app appid=myapp"), + ok = file:make_dir("ebin"), + ok = file:write_file("src/myapp_mymod1.erl", ?myapp_mymod1), + ok = file:write_file("src/myapp_mymod2.erl", ?myapp_mymod2). + +teardown(_) -> + ok = file:set_cwd(".."), + ok = remove_tmp_dir(). + +remove_tmp_dir() -> + remove_tmp_dir(arg_for_eunit). + +remove_tmp_dir(_) -> + ok = rebar_file_utils:rm_rf(?TMP_DIR). + +%% ==================================================================== +%% Helper Functions +%% ==================================================================== + +prepare_rebar_script() -> + Rebar = ?TMP_DIR ++ "rebar", + {ok, _} = file:copy(?REBAR_SCRIPT, Rebar), + case os:type() of + {unix, _} -> + [] = os:cmd("chmod u+x " ++ Rebar); + {win32, _} -> + {ok, _} = file:copy(?REBAR_SCRIPT ++ ".cmd", + ?TMP_DIR ++ "rebar.cmd") + end. + +rebar() -> + rebar([]). + +rebar(Args) when is_list(Args) -> + Out = os:cmd(filename:nativename("./rebar") ++ " " ++ Args), + %% ?debugMsg("**** Begin"), ?debugMsg(Out), ?debugMsg("**** End"), + Out. + +assert_dirs_in(Name, [Dir|T]) -> + [{Name ++ " has directory: " ++ Dir, ?_assert(filelib:is_dir(Dir))} | + assert_dirs_in(Name, T)]; +assert_dirs_in(_, []) -> []. + +assert_files_in(Name, [File|T]) -> + [{Name ++ " has file: " ++ File, ?_assert(filelib:is_regular(File))} | + assert_files_in(Name, T)]; +assert_files_in(_, []) -> []. + +assert_files_not_in(Name, [File|T]) -> + [{Name ++ " does not have file: " ++ File, + ?_assertNot(filelib:is_regular(File))} | assert_files_not_in(Name, T)]; +assert_files_not_in(_, []) -> []. + diff -Nru rebar-2.0.0/test/rebar_eunit_tests.erl rebar-2.6.0/test/rebar_eunit_tests.erl --- rebar-2.0.0/test/rebar_eunit_tests.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/rebar_eunit_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -59,36 +59,223 @@ ?_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}, + {"Ensure EUnit runs a specific test by qualified function name", + setup, + fun() -> + setup_project_with_multiple_modules(), + rebar("-v eunit tests=myapp_mymod:myprivate_test") + end, + fun teardown/1, + fun(RebarOut) -> + [{"Selected test is run", + [?_assert(string:str(RebarOut, + "myapp_mymod:myprivate_test/0") + =/= 0)]}, + + {"Only selected test is run", + [?_assert(string:str(RebarOut, + "Test passed.") =/= 0)]}] + end}, + {"Ensure EUnit runs a specific test by qualified function " + ++ "name and tests from other module", + setup, + fun() -> + setup_project_with_multiple_modules(), + rebar("-v eunit suites=myapp_mymod3 " + ++ "tests=myapp_mymod:myprivate_test") + end, + fun teardown/1, + fun(RebarOut) -> + [{"Selected test is run", + [?_assert(string:str(RebarOut, + "myapp_mymod:myprivate_test/0") =/= 0)]}, + + {"Tests from module are run", + [?_assert(string:str(RebarOut, + "myapp_mymod3:") =/= 0)]}, + + {"Only selected tests are run", + [?_assert(string:str(RebarOut, + "Failed: 1. Skipped: 0. Passed: 1") + =/= 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())}, - - {"Only production modules get coverage reports", - assert_files_not_in("the temporary eunit directory", - [".eunit/myapp_mymod_tests.COVER.html"])}]}. + 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"])}] + 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", @@ -138,6 +325,62 @@ ["test/myapp_mymod_tests.erl", "src/myapp_mymod.erl"])}. +code_path_test_() -> + [{"Ensuring that fast code path cleanup is correct for adds", + setup, fun make_tmp_dir/0, + fun(_) -> remove_tmp_dir() end, + fun() -> + OPath = code:get_path(), + PathZ = ?TMP_DIR ++ "some_path", + PathA = ?TMP_DIR ++ "some_other_path", + ok = file:make_dir(PathZ), + ok = file:make_dir(PathA), + true = code:add_pathz(PathZ), + true = code:add_patha(PathA), + %% make sure that they've been added + ?assertEqual([PathA] ++ OPath ++ [PathZ], + code:get_path()), + true = rebar_utils:cleanup_code_path(OPath), + ?assertEqual(OPath, code:get_path()) + end}, + {"Ensuring that fast code path cleanup is correct for removes", + setup, fun make_tmp_dir/0, + fun(_) -> remove_tmp_dir() end, + fun() -> + OPath = code:get_path(), + Path1 = lists:nth(10, OPath), + Path2 = lists:nth(11, OPath), + true = code:del_path(Path1), + true = code:del_path(Path2), + %% make sure that they've been added + ?assertEqual(OPath -- [Path1, Path2], + code:get_path()), + true = rebar_utils:cleanup_code_path(OPath), + ?assertEqual(OPath, code:get_path()) + end}, + {"Ensuring that fast code path cleanup is equivalent for adds", + setup, fun make_tmp_dir/0, + fun(_) -> remove_tmp_dir() end, + fun() -> + OPath = code:get_path(), + PathZ = ?TMP_DIR ++ "some_path", + PathA = ?TMP_DIR ++ "some_other_path", + ok = file:make_dir(PathZ), + ok = file:make_dir(PathA), + true = code:add_pathz(PathZ), + true = code:add_patha(PathA), + %% make sure that they've been added + ?assertEqual([PathA] ++ OPath ++ [PathZ], + code:get_path()), + true = rebar_utils:cleanup_code_path(OPath), + CleanedPath = code:get_path(), + true = code:add_pathz(PathZ), + true = code:add_patha(PathA), + true = code:set_path(OPath), + ?assertEqual(CleanedPath, code:get_path()) + end}]. + + %% ==================================================================== %% Setup and Teardown %% ==================================================================== @@ -155,6 +398,28 @@ "-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", @@ -168,7 +433,15 @@ "myfunc_test() -> ?assertMatch(ok, myapp_mymod:myfunc()).\n"]). make_tmp_dir() -> - ok = file:make_dir(?TMP_DIR). + case file:make_dir(?TMP_DIR) of + ok -> + ok; + {error, eexist} -> + remove_tmp_dir(), + make_tmp_dir(); + Error -> + throw(Error) + end. setup_environment() -> ok = make_tmp_dir(), @@ -183,6 +456,12 @@ 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"). @@ -214,8 +493,8 @@ {unix, _} -> [] = os:cmd("chmod u+x " ++ Rebar); {win32, _} -> - {ok, _} = file:copy(?REBAR_SCRIPT ++ ".bat", - ?TMP_DIR ++ "rebar.bat") + {ok, _} = file:copy(?REBAR_SCRIPT ++ ".cmd", + ?TMP_DIR ++ "rebar.cmd") end. rebar() -> @@ -247,6 +526,5 @@ 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. diff -Nru rebar-2.0.0/test/rebar_file_utils_tests.erl rebar-2.6.0/test/rebar_file_utils_tests.erl --- rebar-2.0.0/test/rebar_file_utils_tests.erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/rebar_file_utils_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -35,9 +35,14 @@ -include_lib("eunit/include/eunit.hrl"). -define(TMP_DIR, "tmp_file_utils"). --define(DIR_TREE, [{d,"source",[{f,"file1"}, - {f,"file2"}]}, - {d,"dest",[]}]). + +-define(SRC, "source dir?"). +-define(DST, "dest (dir)"). +-define(FILE1, "file 1"). +-define(FILE2, "file(2)"). +-define(DIR_TREE, [{d,?SRC,[{f,?FILE1}, + {f,?FILE2}]}, + {d,?DST,[]}]). -define(FILE_CONTENT, <<"1234567890">>). %% ==================================================================== @@ -57,7 +62,7 @@ rebar_file_utils:delete_each(file_list()) end, fun teardown/1, - [assert_files_not_in("source", file_list())]}. + [assert_files_not_in(?SRC, file_list())]}. %% ==================================================================== %% rm_rf tests @@ -68,20 +73,20 @@ setup, fun() -> setup(), - rebar_file_utils:rm_rf(filename:join([?TMP_DIR,"source","file*"])) + rebar_file_utils:rm_rf(filename:join([?TMP_DIR,?SRC,"file*"])) end, fun teardown/1, - [assert_files_not_in("source", file_list())]}. + [assert_files_not_in(?SRC, file_list())]}. rm_rf_dir_test_() -> {"rm_rf removes directory tree", setup, fun() -> setup(), - rebar_file_utils:rm_rf(filename:join([?TMP_DIR,"source"])) + rebar_file_utils:rm_rf(filename:join([?TMP_DIR,?SRC])) end, fun teardown/1, - [?_assertNot(filelib:is_dir(filename:join([?TMP_DIR,"source"])))]}. + [?_assertNot(filelib:is_dir(filename:join([?TMP_DIR,?SRC])))]}. %% ==================================================================== %% cp_r tests @@ -92,47 +97,47 @@ setup, fun() -> setup(), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source","file1"])], - filename:join([?TMP_DIR,"dest","new_file"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC,?FILE1])], + filename:join([?TMP_DIR,?DST,"new_file"])) end, fun teardown/1, - [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","new_file"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,?DST,"new_file"])))]}. cp_r_file_to_dir_test_() -> {"cp_r copies a file to directory", setup, fun() -> setup(), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source","file1"])], - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC,?FILE1])], + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, - [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,?DST,?FILE1])))]}. cp_r_dir_to_dir_test_() -> {"cp_r copies a directory to directory", setup, fun() -> setup(), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source"])], - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC])], + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, - [?_assert(filelib:is_dir(filename:join([?TMP_DIR,"dest","source"]))), + [?_assert(filelib:is_dir(filename:join([?TMP_DIR,?DST,?SRC]))), assert_files_in("dest/source", - [filename:join([?TMP_DIR,"dest","source",F]) || - F <- ["file1","file2"]])]}. + [filename:join([?TMP_DIR,?DST,?SRC,F]) || + F <- [?FILE1,?FILE2]])]}. cp_r_wildcard_file_to_dir_test_() -> {"cp_r copies wildcard files to directory", setup, fun() -> setup(), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source","*1"])], - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC,"*1"])], + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, - [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,?DST,?FILE1])))]}. cp_r_wildcard_dir_to_dir_test_() -> {"cp_r copies wildcard directory to directory", @@ -140,45 +145,45 @@ fun() -> setup(), rebar_file_utils:cp_r([filename:join([?TMP_DIR,"sour*"])], - filename:join([?TMP_DIR,"dest"])) + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, - [?_assert(filelib:is_dir(filename:join([?TMP_DIR,"dest","source"]))), + [?_assert(filelib:is_dir(filename:join([?TMP_DIR,?DST,?SRC]))), assert_files_in("dest/source", - [filename:join([?TMP_DIR,"dest","source",F]) || - F <- ["file1","file2"]])]}. + [filename:join([?TMP_DIR,?DST,?SRC,F]) || + F <- [?FILE1,?FILE2]])]}. cp_r_overwrite_file_test_() -> {"cp_r overwrites destination file", setup, fun() -> setup(), - ok = file:write_file(filename:join([?TMP_DIR,"dest","file1"]), + ok = file:write_file(filename:join([?TMP_DIR,?DST,?FILE1]), <<"test">>), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source","file1"])], - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC,?FILE1])], + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, [?_assertMatch({ok,?FILE_CONTENT}, file:read_file( - filename:join([?TMP_DIR,"dest","file1"])))]}. + filename:join([?TMP_DIR,?DST,?FILE1])))]}. cp_r_overwrite_dir_test_() -> {"cp_r overwrites destination file (xcopy case on win32)", setup, fun() -> setup(), - ok = file:make_dir(filename:join([?TMP_DIR,"dest","source"])), + ok = file:make_dir(filename:join([?TMP_DIR,?DST,?SRC])), ok = file:write_file( - filename:join([?TMP_DIR,"dest","source","file1"]), + filename:join([?TMP_DIR,?DST,?SRC,?FILE1]), <<"test">>), - rebar_file_utils:cp_r([filename:join([?TMP_DIR,"source"])], - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:cp_r([filename:join([?TMP_DIR,?SRC])], + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, [?_assertMatch({ok,?FILE_CONTENT}, file:read_file( - filename:join([?TMP_DIR,"dest","source","file1"])))]}. + filename:join([?TMP_DIR,?DST,?SRC,?FILE1])))]}. cp_r_overwrite_file_fail_test_() -> {"cp_r fails to fs permission errors (file:copy/2 case on win32)", @@ -186,15 +191,15 @@ fun() -> setup(), ok = file:write_file( - filename:join([?TMP_DIR,"dest","file1"]),<<"test">>), + filename:join([?TMP_DIR,?DST,?FILE1]),<<"test">>), ok = file:change_mode( - filename:join([?TMP_DIR,"dest","file1"]),0) + filename:join([?TMP_DIR,?DST,?FILE1]),0) end, fun teardown/1, - [?_assertError({badmatch,_}, + [?_assertThrow(rebar_abort, rebar_file_utils:cp_r( - [filename:join([?TMP_DIR,"source","file1"])], - filename:join([?TMP_DIR,"dest"])))]}. + [filename:join([?TMP_DIR,?SRC,?FILE1])], + filename:join([?TMP_DIR,?DST])))]}. cp_r_overwrite_dir_fail_test_() -> {"cp_r fails to fs permission error (xcopy case on win32)", @@ -202,50 +207,49 @@ fun() -> setup(), ok = file:make_dir( - filename:join([?TMP_DIR,"dest","source"])), + filename:join([?TMP_DIR,?DST,?SRC])), ok = file:write_file( - filename:join([?TMP_DIR,"dest","source","file1"]), + filename:join([?TMP_DIR,?DST,?SRC,?FILE1]), <<"test">>), ok = file:change_mode( - filename:join([?TMP_DIR,"dest","source","file1"]),0) + filename:join([?TMP_DIR,?DST,?SRC,?FILE1]),0) end, fun teardown/1, - [?_assertError({badmatch,_}, + [?_assertThrow(rebar_abort, rebar_file_utils:cp_r( - [filename:join([?TMP_DIR,"source"])], - filename:join([?TMP_DIR,"dest"])))]}. + [filename:join([?TMP_DIR,?SRC])], + filename:join([?TMP_DIR,?DST])))]}. mv_file_test_() -> {"move a file to folder", setup, fun() -> setup(), - rebar_file_utils:mv(filename:join([?TMP_DIR,"source","file1"]), - filename:join([?TMP_DIR,"dest"])) + rebar_file_utils:mv(filename:join([?TMP_DIR,?SRC,?FILE1]), + filename:join([?TMP_DIR,?DST])) end, fun teardown/1, - [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"]))), + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,?DST,?FILE1]))), ?_assertNot(filelib:is_regular( - filename:join([?TMP_DIR,"source","file1"])))]}. + filename:join([?TMP_DIR,?SRC,?FILE1])))]}. %% ==================================================================== %% Utilities %% ==================================================================== file_list() -> - [filename:join([?TMP_DIR,"source",F]) || F <- ["file1","file2"]]. + [filename:join([?TMP_DIR,?SRC,F]) || F <- [?FILE1,?FILE2]]. %% ==================================================================== %% Setup and Teardown %% ==================================================================== setup() -> - file:make_dir(?TMP_DIR), make_dir_tree(?TMP_DIR,?DIR_TREE). make_dir_tree(Parent, [{d,Dir,Contents} | Rest]) -> NewDir = filename:join(Parent,Dir), - ok = file:make_dir(NewDir), + ok = filelib:ensure_dir(filename:join(NewDir, "tmp")), ok = make_dir_tree(NewDir,Contents), ok = make_dir_tree(Parent,Rest); make_dir_tree(Parent, [{f,File} | Rest]) -> diff -Nru rebar-2.0.0/test/rebar_otp_release_tests.erl rebar-2.6.0/test/rebar_otp_release_tests.erl --- rebar-2.0.0/test/rebar_otp_release_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/test/rebar_otp_release_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,47 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tuncer Ayaz +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_otp_release_tests). + +-include_lib("eunit/include/eunit.hrl"). + +check_otp_release_test() -> + case rebar_utils:otp_release() of + %% <= R16 + [$R,N|_] when is_integer(N) -> + ?assert(true); + %% >= 17.x + [N|_]=Rel when is_integer(N) -> + %% Check that it has at least Major.Minor. + ?assert(length(string:tokens(Rel, ".")) > 1), + + %% If otp_patch_apply was used and the release version has + %% a "**" suffix, we drop that part in otp_release/0. + ?assertEqual(0, string:str(Rel, "*")), + + %% Check that "\n" is dropped in otp_release/0. + ?assertEqual(0, string:str(Rel, "\n")) + end. diff -Nru rebar-2.0.0/test/rebar_require_vsn_tests.erl rebar-2.6.0/test/rebar_require_vsn_tests.erl --- rebar-2.0.0/test/rebar_require_vsn_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/test/rebar_require_vsn_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,28 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(rebar_require_vsn_tests). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +version_tuple_test_() -> + [%% typical cases + ?_assert(check("R15B", "eunit") =:= {15, 0}), + ?_assert(check("R15B01", "eunit") =:= {15, 1}), + ?_assert(check("R15B02", "eunit") =:= {15, 2}), + ?_assert(check("R15B03-1", "eunit") =:= {15, 3}), + ?_assert(check("R15B03", "eunit") =:= {15, 3}), + ?_assert(check("R16B", "eunit") =:= {16, 0}), + ?_assert(check("R16B01", "eunit") =:= {16, 1}), + ?_assert(check("R16B02", "eunit") =:= {16, 2}), + ?_assert(check("R16B03", "eunit") =:= {16, 3}), + ?_assert(check("R16B03-1", "eunit") =:= {16, 3}), + ?_assert(check("17", "eunit") =:= {17, 0}), + %% error cases + ?_assertException(throw, rebar_abort, check("", "eunit")), + ?_assertException(throw, rebar_abort, check("abc", "eunit")) + ]. + +check(OtpRelease, Type) -> + rebar_require_vsn:version_tuple(abort, OtpRelease, Type). diff -Nru rebar-2.0.0/test/rebar_utils_tests.erl rebar-2.6.0/test/rebar_utils_tests.erl --- rebar-2.0.0/test/rebar_utils_tests.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/test/rebar_utils_tests.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,50 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2015 Mark Anderson +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +%% @author Mark Anderson +%% @doc Tests functionality of rebar_utils module. +%% @copyright 2015 Mark Anderson +%% ------------------------------------------------------------------- +-module(rebar_utils_tests). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +expand_env_variable_test_() -> + [{"Ensure that variable expansion handles nonwhitespace", + fun() -> + Test = "$V $V/ $Vw", + ?assertEqual("Val Val/ $Vw", + rebar_utils:expand_env_variable(Test, "V", "Val") ) + end}, + {"Ensure that variable expansion with braces works", + fun() -> + Test = "${V} ${V}/ ${V}w ${Vw}", + ?assertEqual("Val Val/ Valw ${Vw}", + rebar_utils:expand_env_variable(Test, "V", "Val") ) + end}]. + diff -Nru rebar-2.0.0/test/rebar_xref_eunit.erl rebar-2.6.0/test/rebar_xref_eunit.erl --- rebar-2.0.0/test/rebar_xref_eunit.erl 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/test/rebar_xref_eunit.erl 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,205 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +-module(rebar_xref_eunit). + +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +-define(REBAR_SCRIPT, "../rebar"). + +-define(TMP_DIR, "tmp_xref_eunit/"). + +xref_test_() -> + {"Test the various xref warnings", + setup, fun() -> setup_project(false), rebar("compile"), rebar("skip_deps=true xref") end, + fun teardown/1, + fun(RebarOut) -> + [ + {"Undefined function", ?_assert(string:str(RebarOut, + "myapp_somemod:notavailable/1 is undefined function") =/= 0)}, + {"Undefined function call", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 calls undefined function myapp_somemod:notavailable/1") =/= 0)}, + {"Deprecated function", ?_assert(string:str(RebarOut, + "myapp_mymod:fdeprecated/0 is deprecated function") =/= 0)}, + {"Deprecated function call", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 calls deprecated function myapp_mymod:fdeprecated/0") =/= 0)}, + {"Unused local", ?_assert(string:str(RebarOut, + "myapp_mymod:localfunc2/0 is unused local function") =/= 0)}, + {"Unused export 1", ?_assert(string:str(RebarOut, + "myapp_behaviour1:behaviour_info/1 is unused export") =/= 0)}, + {"Unused export 2", ?_assert(string:str(RebarOut, + "myapp_behaviour2:behaviour_info/1 is unused export") =/= 0)}, + {"Unused export 3", ?_assert(string:str(RebarOut, + "myapp_mymod:other2/1 is unused export") =/= 0)}, + {"Unused export 4", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 is unused export") =/= 0)}, + {"Suppressed behaviour export 1", ?_assert(string:str(RebarOut, + "myapp_mymod:bh1_a/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 2", ?_assert(string:str(RebarOut, + "myapp_mymod:bh1_b/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 3", ?_assert(string:str(RebarOut, + "myapp_mymod:bh2_a/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 4", ?_assert(string:str(RebarOut, + "myapp_mymod:bh2_b/1 is unused export") =:= 0)} + ] + end}. + +xref_ignore_test_() -> + {"Test the suppression of xref warnings", + setup, fun() -> setup_project(ignore_xref), rebar("compile"), rebar("skip_deps=true xref") end, + fun teardown/1, + fun(RebarOut) -> + [ + {"Undefined function can not be suppressed.", ?_assert(string:str(RebarOut, + "myapp_somemod:notavailable/1 is undefined function") =/= 0)}, + {"Supppressed undefined function call", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 calls undefined function myapp_somemod:notavailable/1") =:= 0)}, + {"Supppressed deprecated function", ?_assert(string:str(RebarOut, + "myapp_mymod:fdeprecated/0 is deprecated function") =:= 0)}, + {"Supppressed deprecated function call", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 calls deprecated function myapp_mymod:fdeprecated/0") =:= 0)}, + {"Supppressed unused local", ?_assert(string:str(RebarOut, + "myapp_mymod:localfunc2/0 is unused local function") =:= 0)}, + {"Supppressed unused export 1", ?_assert(string:str(RebarOut, + "myapp_behaviour1:behaviour_info/1 is unused export") =:= 0)}, + {"Supppressed unused export 2", ?_assert(string:str(RebarOut, + "myapp_behaviour2:behaviour_info/1 is unused export") =:= 0)}, + {"Supppressed unused export 3", ?_assert(string:str(RebarOut, + "myapp_mymod:other2/1 is unused export") =:= 0)}, + {"Supppressed unused export 4", ?_assert(string:str(RebarOut, + "myapp_othermod:somefunc/0 is unused export") =:= 0)}, + {"Suppressed behaviour export 1", ?_assert(string:str(RebarOut, + "myapp_mymod:bh1_a/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 2", ?_assert(string:str(RebarOut, + "myapp_mymod:bh1_b/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 3", ?_assert(string:str(RebarOut, + "myapp_mymod:bh2_a/1 is unused export") =:= 0)}, + {"Suppressed behaviour export 4", ?_assert(string:str(RebarOut, + "myapp_mymod:bh2_b/1 is unused export") =:= 0)} + ] + + end}. + + +%% ==================================================================== +%% Setup and Teardown +%% ==================================================================== + +-define(myapp_behaviour1, + ["-module(myapp_behaviour1).\n", + "-export([behaviour_info/1]).\n"]). +-define(myapp_behaviour1_body, + ["behaviour_info(callbacks) -> [{bh1_a,1},{bh1_b,1}];\n", + "behaviour_info(_Other) -> undefined.\n"]). +-define(myapp_behaviour1_ignorexref, + ["-ignore_xref({behaviour_info,1}).\n"]). + +-define(myapp_behaviour2, + ["-module(myapp_behaviour2).\n", + "-export([behaviour_info/1]).\n"]). +-define(myapp_behaviour2_body, + ["behaviour_info(callbacks) -> [{bh2_a,1},{bh2_b,1}];\n", + "behaviour_info(_Other) -> undefined.\n"]). +-define(myapp_behaviour2_ignorexref, + ["-ignore_xref({behaviour_info,1}).\n"]). + +-define(myapp_mymod, + ["-module(myapp_mymod).\n", + "-export([bh1_a/1,bh1_b/1,bh2_a/1,bh2_b/1,other1/1,other2/1,fdeprecated/0]).\n", + "-behaviour(myapp_behaviour1).\n", % 2 behaviours + "-behaviour(myapp_behaviour2).\n", + "-deprecated({fdeprecated,0}).\n"]). % deprecated function +-define(myapp_mymod_body, + ["bh1_a(A) -> localfunc1(bh1_a, A).\n", % behaviour functions + "bh1_b(A) -> localfunc1(bh1_b, A).\n", + "bh2_a(A) -> localfunc1(bh2_a, A).\n", + "bh2_b(A) -> localfunc1(bh2_b, A).\n", + "other1(A) -> localfunc1(other1, A).\n", % regular exported functions + "other2(A) -> localfunc1(other2, A).\n", + "localfunc1(A, B) -> {A, B}.\n", % used local + "localfunc2() -> ok.\n", % unused local + "fdeprecated() -> ok.\n" % deprecated function + ]). +-define(myapp_mymod_ignorexref, + ["-ignore_xref([{other2,1},{localfunc2,0},{fdeprecated,0}]).\n"]). + + + +-define(myapp_othermod, + ["-module(myapp_othermod).\n", + "-export([somefunc/0]).\n"]). +-define(myapp_othermod_body, + ["somefunc() ->\n", + " myapp_mymod:other1(arg),\n", + " myapp_somemod:notavailable(arg),\n", + " myapp_mymod:fdeprecated().\n" + ]). +-define(myapp_othermod_ignorexref, + ["-ignore_xref([{myapp_somemod,notavailable,1},{somefunc,0}]).\n", + "-ignore_xref({myapp_mymod,fdeprecated,0}).\n"]). + + +-define(myapp_rebarconfig, + ["{erl_opts, [debug_info]}.\n", + "{xref_checks, [deprecated_function_calls,deprecated_functions,\n", + " undefined_function_calls,undefined_functions,\n", + " exports_not_used,locals_not_used]}.\n" + ]). + +setup_environment() -> + ok = file:make_dir(?TMP_DIR), + prepare_rebar_script(), + ok = file:set_cwd(?TMP_DIR). + +prepare_project() -> + setup_environment(), + rebar("create-app appid=myapp"), + ok = file:make_dir("ebin"). + +setup_project(ignore_xref) -> + prepare_project(), + ok = file:write_file("src/myapp_behaviour1.erl", ?myapp_behaviour1 ++ ?myapp_behaviour1_ignorexref ++ ?myapp_behaviour1_body), + ok = file:write_file("src/myapp_behaviour2.erl", ?myapp_behaviour2 ++ ?myapp_behaviour2_ignorexref++ ?myapp_behaviour2_body), + ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod ++ ?myapp_mymod_ignorexref ++ ?myapp_mymod_body), + ok = file:write_file("src/myapp_othermod.erl", ?myapp_othermod ++ ?myapp_othermod_ignorexref ++ ?myapp_othermod_body), + ok = file:write_file("rebar.config", ?myapp_rebarconfig); + +setup_project(_) -> + prepare_project(), + ok = file:write_file("src/myapp_behaviour1.erl", ?myapp_behaviour1 ++ ?myapp_behaviour1_body), + ok = file:write_file("src/myapp_behaviour2.erl", ?myapp_behaviour2 ++ ?myapp_behaviour2_body), + ok = file:write_file("src/myapp_mymod.erl", ?myapp_mymod ++ ?myapp_mymod_body), + ok = file:write_file("src/myapp_othermod.erl", ?myapp_othermod ++ ?myapp_othermod_body), + ok = file:write_file("rebar.config", ?myapp_rebarconfig). + + +teardown(_) -> + ok = file:set_cwd(".."), + ok = remove_tmp_dir(). + +remove_tmp_dir() -> + ok = rebar_file_utils:rm_rf(?TMP_DIR). + +%% ==================================================================== +%% Helper Functions +%% ==================================================================== + +prepare_rebar_script() -> + Rebar = ?TMP_DIR ++ "rebar", + {ok, _} = file:copy(?REBAR_SCRIPT, Rebar), + case os:type() of + {unix, _} -> + [] = os:cmd("chmod u+x " ++ Rebar); + {win32, _} -> + {ok, _} = file:copy(?REBAR_SCRIPT ++ ".bat", + ?TMP_DIR ++ "rebar.bat") + end. + +rebar() -> + rebar([]). + +rebar(Args) when is_list(Args) -> + Out = os:cmd(filename:nativename("./rebar") ++ " " ++ Args), + %% ?debugMsg("**** Begin"), ?debugMsg(Out), ?debugMsg("**** End"), + Out. diff -Nru rebar-2.0.0/test/upgrade_project/README.md rebar-2.6.0/test/upgrade_project/README.md --- rebar-2.0.0/test/upgrade_project/README.md 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/README.md 2015-06-19 16:14:28.000000000 +0000 @@ -1,7 +1,9 @@ #### Building version 0.1 rebar compile + cd rel rebar generate - mv rel/dummy rel/dummy_0.1 + mv dummy dummy_0.1 + cd .. rebar clean # start the release: cd rel/dummy_0.1 @@ -20,15 +22,17 @@ $EDITOR rel/reltool.config rebar compile + cd rel rebar generate # previous_release path is relative to your rel directory rebar generate-appups previous_release=dummy_0.1 rebar generate-upgrade previous_release=dummy_0.1 - tar -zvtf rel/dummy_0.2.tar.gz + tar -zvtf dummy_0.2.tar.gz + mv dummy dummy_0.2 #### Deploying with release_handler - mv rel/dummy_0.2.tar.gz rel/dummy_0.1/releases/ + mv dummy_0.2.tar.gz dummy_0.1/releases/ # Now use release_handler in the running erlang console for the deploy: @@ -38,3 +42,9 @@ erl> release_handler:which_releases(). erl> dummy_server:get_state(). + +#### Building version 0.3 + rm -r rel/dummy + + # Now repeat steps in 'Building version 0.2' and 'Deploying with release_handler' + # while replacing '0.2' by '0.3' and '0.1' by '0.2'. diff -Nru rebar-2.0.0/test/upgrade_project/rel/files/dummy rebar-2.6.0/test/upgrade_project/rel/files/dummy --- rebar-2.0.0/test/upgrade_project/rel/files/dummy 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/rel/files/dummy 2015-06-19 16:14:28.000000000 +0000 @@ -2,25 +2,41 @@ # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et -RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) +# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. +if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then + POSIX_SHELL="true" + export POSIX_SHELL + # To support 'whoami' add /usr/ucb to path + PATH=/usr/ucb:$PATH + export PATH + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh +unset POSIX_SHELL + +RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd -P) + +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= +WHOAMI=$(whoami) # Make sure this script is running as the appropriate user -if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then - exec sudo -u $RUNNER_USER -i $0 $@ +if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then + type sudo > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 + exit 1 + fi + echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 + exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ 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,22 +45,37 @@ 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 -NAME_ARG=`egrep '^-s?name' $VMARGS_PATH` +NAME_ARG=`egrep '^\-s?name' $VMARGS_PATH` if [ -z "$NAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 @@ -54,17 +85,24 @@ REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` -# Note the `date +%s`, used to allow multiple remsh to the same node transparently -REMSH_NAME_ARG="$REMSH_TYPE attach`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`" +# Note the `date +%s`, used to allow multiple remsh to the same node +# transparently +REMSH_NAME_ARG="$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`" REMSH_REMSH_ARG="-remsh $REMSH_NAME" # Extract the target cookie -COOKIE_ARG=`grep '^-setcookie' $VMARGS_PATH` +COOKIE_ARG=`grep '^\-setcookie' $VMARGS_PATH` if [ -z "$COOKIE_ARG" ]; then echo "vm.args needs to have a -setcookie parameter." 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 @@ -74,47 +112,83 @@ # Setup remote shell command to control node REMSH="$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG" +# Common functions + +# Ping node without allowing nodetool to take stdin +ping_node() { + $NODETOOL ping < /dev/null +} + +# Set the PID global variable, return 1 on error +get_pid() { + PID=`$NODETOOL getpid < /dev/null` + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + return 1 + fi + + # don't allow empty or init pid's + if [ -z $PID ] || [ "$PID" -le 1 ]; then + return 1 + fi + + return 0 +} + # 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` + RES=`ping_node` 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) # Wait for the node to completely stop... case `uname -s` in - Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) - # PID COMMAND - PID=`ps ax -o pid= -o command=|\ - grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` - ;; - SunOS) - # PID COMMAND - PID=`ps -ef -o pid= -o args=|\ - grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` - ;; - CYGWIN*) - # UID PID PPID TTY STIME COMMAND - PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` - ;; + Darwin) + # Make sure we explicitly set this because iTerm.app doesn't for + # some reason. + COMMAND_MODE=unix2003 esac + + # Get the PID from nodetool + get_pid + GPR=$? + if [ "$GPR" -ne 0 ] || [ -z $PID ]; then + exit $GPR + fi + + # Tell nodetool to initiate a stop $NODETOOL stop ES=$? if [ "$ES" -ne 0 ]; then exit $ES fi - while `kill -0 $PID 2>/dev/null`; + + # Wait for the node to completely stop... + while `kill -s 0 $PID 2>/dev/null` do sleep 1 done @@ -140,7 +214,7 @@ ping) ## See if the VM is alive - $NODETOOL ping + ping_node ES=$? if [ "$ES" -ne 0 ]; then exit $ES @@ -148,8 +222,8 @@ ;; attach) - # Make sure a node IS running - RES=`$NODETOOL ping` + # Make sure a node is running + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" @@ -160,9 +234,26 @@ exec $ERTS_PATH/to_erl $PIPE_DIR ;; - remote_console) + eval) # Make sure a node IS running - RES=`$NODETOOL ping` + ping_node > /dev/null 2>&1 + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + shift + $NODETOOL eval "$1" + ES_EVAL=$? + if [ "$ES_EVAL" -ne 0 ]; then + exit $ES_EVAL + fi + ;; + + remote_console) + # Make sure a node is running + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" @@ -182,25 +273,28 @@ fi # Make sure a node IS running - RES=`$NODETOOL ping` + ping_node ES=$? if [ "$ES" -ne 0 ]; then echo "Node is not running!" exit $ES fi - node_name=`echo $NAME_ARG | awk '{print $2}'` - erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` - - $ERTS_PATH/escript $RUNNER_BASE_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2 + $NODETOOL upgrade $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 @@ -249,8 +343,17 @@ # Start the VM exec $CMD -- ${1+"$@"} ;; + getpid) + # Get the PID from nodetool + get_pid + ES=$? + if [ "$ES" -ne 0 ] || [ -z $PID ]; then + exit $ES + fi + echo $PID + ;; *) - echo "Usage: $SCRIPT {start|foreground|stop|restart|reboot|ping|console|console_clean|attach|remote_console|upgrade}" + echo "Usage: $SCRIPT {start|start_boot |foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot |attach|eval|remote_console|upgrade}" exit 1 ;; esac diff -Nru rebar-2.0.0/test/upgrade_project/rel/files/erl rebar-2.6.0/test/upgrade_project/rel/files/erl --- rebar-2.0.0/test/upgrade_project/rel/files/erl 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/rel/files/erl 2015-06-19 16:14:28.000000000 +0000 @@ -1,16 +1,26 @@ #!/bin/sh -## This script replaces the default "erl" in erts-VSN/bin. This is necessary -## as escript depends on erl and in turn, erl depends on having access to a -## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect -## of running escript -- the embedded node bypasses erl and uses erlexec directly -## (as it should). +# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. +if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then + POSIX_SHELL="true" + export POSIX_SHELL + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh as well +unset POSIX_SHELL + +## This script replaces the default "erl" in erts-VSN/bin. This is +## necessary as escript depends on erl and in turn, erl depends on +## having access to a bootscript (start.boot). Note that this script +## is ONLY invoked as a side-effect of running escript -- the embedded +## node bypasses erl and uses erlexec directly (as it should). ## -## Note that this script makes the assumption that there is a start_clean.boot -## file available in $ROOTDIR/release/VSN. +## Note that this script makes the assumption that there is a +## start_clean.boot file available in $ROOTDIR/release/VSN. # Determine the abspath of where this script is executing from. -ERTS_BIN_DIR=$(cd ${0%/*} && pwd) +ERTS_BIN_DIR=$(cd ${0%/*} && pwd -P) # Now determine the root directory -- this script runs from erts-VSN/bin, # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR diff -Nru rebar-2.0.0/test/upgrade_project/rel/files/nodetool rebar-2.6.0/test/upgrade_project/rel/files/nodetool --- rebar-2.0.0/test/upgrade_project/rel/files/nodetool 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/rel/files/nodetool 2015-06-19 16:14:28.000000000 +0000 @@ -1,3 +1,4 @@ +#!/usr/bin/env escript %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et %% ------------------------------------------------------------------- @@ -5,25 +6,51 @@ %% nodetool: Helper Script for interacting with live nodes %% %% ------------------------------------------------------------------- - main(Args) -> ok = start_epmd(), %% Extract the args {RestArgs, TargetNode} = process_args(Args, [], undefined), + %% any commands that don't need a running node + case RestArgs of + ["chkconfig", File] -> + case file:consult(File) of + {ok, _} -> + io:format("ok\n"), + halt(0); + {error, {Line, Mod, Term}} -> + io:format(standard_error, ["Error on line ", + file:format_error({Line, Mod, Term}), "\n"], []), + halt(1); + {error, R} -> + io:format(standard_error, ["Error reading config file: ", + file:format_error(R), "\n"], []), + halt(1) + end; + _ -> + ok + end, + %% See if the node is currently running -- if it's not, we'll bail - case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + case {net_kernel:hidden_connect_node(TargetNode), + net_adm:ping(TargetNode)} of {true, pong} -> ok; + {false,pong} -> + io:format("Failed to connect to node ~p .\n", [TargetNode]), + halt(1); {_, pang} -> io:format("Node ~p not responding to pings.\n", [TargetNode]), halt(1) end, case RestArgs of + ["getpid"] -> + io:format("~p\n", + [list_to_integer(rpc:call(TargetNode, os, getpid, []))]); ["ping"] -> - %% If we got this far, the node already responsed to a ping, so just dump - %% a "pong" + %% If we got this far, the node already responsed to a + %% ping, so just dump a "pong" io:format("pong\n"); ["stop"] -> io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); @@ -32,7 +59,9 @@ ["reboot"] -> io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); ["rpc", Module, Function | RpcArgs] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), [RpcArgs], 60000) of ok -> ok; @@ -42,8 +71,23 @@ _ -> halt(1) end; + ["rpc_infinity", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), + [RpcArgs], infinity) of + ok -> + ok; + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; ["rpcterms", Module, Function, ArgsAsString] -> - case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), consult(ArgsAsString), 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), @@ -51,9 +95,23 @@ Other -> io:format("~p\n", [Other]) end; + ["eval", Str0] -> + Str = string:strip(Str0, right, $.) ++ ".", + Bindings = erl_eval:new_bindings(), + case rpc:call(TargetNode, + erl_eval, + exprs, + [parse(Str), Bindings], + 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + {value, Value, _Bindings} -> + io:format("~p\n", [Value]) + end; Other -> io:format("Other: ~p\n", [Other]), - io:format("Usage: nodetool {ping|stop|restart|reboot}\n") + io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval}\n") end, net_kernel:stop(). @@ -136,3 +194,8 @@ {more, Cont1} -> consult(Cont1, eof, Acc) end. + +parse(Str) -> + {ok, Tokens, _} = erl_scan:string(Str), + {ok, Exprs} = erl_parse:parse_exprs(Tokens), + Exprs. diff -Nru rebar-2.0.0/test/upgrade_project/rel/files/sys.config rebar-2.6.0/test/upgrade_project/rel/files/sys.config --- rebar-2.0.0/test/upgrade_project/rel/files/sys.config 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/rel/files/sys.config 2015-06-19 16:14:28.000000000 +0000 @@ -8,3 +8,4 @@ {error_logger_mf_maxfiles, 5} % 5 files max ]} ]. + diff -Nru rebar-2.0.0/test/upgrade_project/rel/files/vm.args rebar-2.6.0/test/upgrade_project/rel/files/vm.args --- rebar-2.0.0/test/upgrade_project/rel/files/vm.args 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/test/upgrade_project/rel/files/vm.args 2015-06-19 16:14:28.000000000 +0000 @@ -9,11 +9,11 @@ ##-heart ## Enable kernel poll and a few async threads -+K true -+A 5 +##+K true +##+A 5 ## Increase number of concurrent ports/sockets --env ERL_MAX_PORTS 4096 +##-env ERL_MAX_PORTS 4096 ## Tweak GC to run more often --env ERL_FULLSWEEP_AFTER 10 +##-env ERL_FULLSWEEP_AFTER 10 diff -Nru rebar-2.0.0/THANKS rebar-2.6.0/THANKS --- rebar-2.0.0/THANKS 2012-06-04 14:56:51.000000000 +0000 +++ rebar-2.6.0/THANKS 2015-06-19 16:14:28.000000000 +0000 @@ -82,7 +82,7 @@ Tomas Abrahamsson Francis Joanis fisher@yun.io -Yurin Slava +Slava Yurin Phillip Toland Mike Lazar Loic Hoguin @@ -91,3 +91,54 @@ Amit Kapoor Ulf Wiger Nick Vatamaniuc +Daniel Luna +Motiejus Jakstys +Eric B Merritt +Fred Hebert +Kresten Krab Thorup +David Aaberg +Pedram Nimreezi +Edwin Fine +Lev Walkin +Roberto Ostinelli +Joe DeVivo +Markus Nasman +Dmitriy Kargapolov +Ryan Zezeski +Daniel White +Martin Schut +Serge Aleynikov +Magnus Henoch +Artem Teslenko +Jeremie Lasalle Ratelle +Jose Valim +Krzysztof Rutka +Mats Cronqvist +Matthew Conway +Giacomo Olgeni +Pedram Nimreezi +Sylvain Benner +Oliver Ferrigni +Dave Thomas +Evgeniy Khramtsov +YeJun Su +Yuki Ito +alisdair sullivan +Alexander Verbitsky +Andras Horvath +Drew Varner +Roberto Aloi +Luis Rascao +Vlad Dumitrescu +stwind +Pavel Baturko +Igor Savchuk +Mark Anderson +Brian H. Ward +David Kubecka +Carlos Eduardo de Paula +Paulo F. Oliveira +Derek Brown +Danil Onishchenko +Stavros Aronis +James Fish diff -Nru rebar-2.0.0/.travis.yml rebar-2.6.0/.travis.yml --- rebar-2.0.0/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ rebar-2.6.0/.travis.yml 2015-06-19 16:14:28.000000000 +0000 @@ -0,0 +1,16 @@ +sudo: false +language: erlang +otp_release: + - R16B02 + - R16B01 + - R16B + - R15B01 + - R15B + - R14B04 + - R14B03 + - 17.0 +before_script: + - hostname -f + - cc -v + - ld -v +script: "make travis"