summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml3
-rw-r--r--Makefile6
-rw-r--r--dialyzer_reference2
-rw-r--r--inttest/depplugins/depplugins_rt.erl50
-rw-r--r--inttest/depplugins/rebar.config1
-rw-r--r--inttest/depplugins/rebar_dependsonplugin.config2
-rw-r--r--inttest/depplugins/testplugin_mod.erl6
-rw-r--r--inttest/tdeps1/a.rebar.config2
-rw-r--r--inttest/tdeps1/b.rebar.config2
-rw-r--r--inttest/tdeps1/tdeps1_rt.erl19
-rw-r--r--inttest/tdeps2/a.rebar.config2
-rw-r--r--inttest/tdeps2/b.rebar.config2
-rw-r--r--inttest/tdeps2/tdeps2_rt.erl19
-rw-r--r--priv/templates/simpleapp_sup.erl1
-rw-r--r--priv/templates/simplenode.erl.script26
-rwxr-xr-x[-rw-r--r--]priv/templates/simplenode.nodetool58
-rw-r--r--priv/templates/simplenode.reltool.config8
-rwxr-xr-xpriv/templates/simplenode.runner117
-rw-r--r--priv/templates/simplenode.windows.runner.cmd9
-rw-r--r--rebar.config.sample8
-rw-r--r--src/getopt.erl533
-rw-r--r--src/rebar.erl56
-rw-r--r--src/rebar_abnfc_compiler.erl17
-rw-r--r--src/rebar_appups.erl10
-rw-r--r--src/rebar_asn1_compiler.erl20
-rw-r--r--src/rebar_base_compiler.erl2
-rw-r--r--src/rebar_cleaner.erl17
-rw-r--r--src/rebar_core.erl113
-rw-r--r--src/rebar_ct.erl49
-rw-r--r--src/rebar_deps.erl41
-rw-r--r--src/rebar_dia_compiler.erl20
-rw-r--r--src/rebar_edoc.erl11
-rw-r--r--src/rebar_erlc_compiler.erl43
-rw-r--r--src/rebar_erlydtl_compiler.erl33
-rw-r--r--src/rebar_escripter.erl27
-rw-r--r--src/rebar_eunit.erl42
-rw-r--r--src/rebar_lfe_compiler.erl11
-rw-r--r--src/rebar_neotoma_compiler.erl20
-rw-r--r--src/rebar_otp_app.erl35
-rw-r--r--src/rebar_port_compiler.erl31
-rw-r--r--src/rebar_protobuffs_compiler.erl21
-rw-r--r--src/rebar_qc.erl17
-rw-r--r--src/rebar_reltool.erl55
-rw-r--r--src/rebar_require_vsn.erl22
-rw-r--r--src/rebar_templater.erl48
-rw-r--r--src/rebar_upgrade.erl10
-rw-r--r--src/rebar_utils.erl2
-rw-r--r--src/rebar_xref.erl19
-rw-r--r--test/rebar_eunit_tests.erl14
-rwxr-xr-xtest/upgrade_project/rel/files/dummy187
-rwxr-xr-xtest/upgrade_project/rel/files/erl26
-rw-r--r--test/upgrade_project/rel/files/nodetool58
-rw-r--r--test/upgrade_project/rel/files/sys.config1
-rw-r--r--test/upgrade_project/rel/files/vm.args8
54 files changed, 1568 insertions, 394 deletions
diff --git a/.travis.yml b/.travis.yml
index f5e3d99..191d337 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,8 @@
language: erlang
otp_release:
+ - R16B
- R15B01
- R15B
- R14B04
- R14B03
-script: "make debug xref clean all deps test"
+script: "make travis"
diff --git a/Makefile b/Makefile
index 4ff331f..1144d5e 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ dialyzer: dialyzer_warnings
@diff -U0 dialyzer_reference dialyzer_warnings
dialyzer_warnings:
- -@dialyzer -q -n ebin -Wunmatched_returns -Werror_handling \
+ -@dialyzer -q -nn -n ebin -Wunmatched_returns -Werror_handling \
-Wrace_conditions > dialyzer_warnings
binary: VSN = $(shell ./rebar -V)
@@ -38,4 +38,6 @@ deps:
test:
@$(REBAR) eunit
- @$(RETEST) inttest
+ @$(RETEST) -v inttest
+
+travis: clean debug xref clean all deps test
diff --git a/dialyzer_reference b/dialyzer_reference
index a50bb88..fccada2 100644
--- a/dialyzer_reference
+++ b/dialyzer_reference
@@ -1,3 +1,3 @@
-rebar_eunit.erl:351: Call to missing or unexported function eunit_test:function_wrapper/2
+rebar_eunit.erl:388: Call to missing or unexported function eunit_test:function_wrapper/2
rebar_utils.erl:162: Call to missing or unexported function escript:foldl/3
diff --git a/inttest/depplugins/depplugins_rt.erl b/inttest/depplugins/depplugins_rt.erl
new file mode 100644
index 0000000..7b106eb
--- /dev/null
+++ b/inttest/depplugins/depplugins_rt.erl
@@ -0,0 +1,50 @@
+%%% @doc Plugin handling test
+%%%
+%%% This test checks if plugins are loaded correctly.
+%%%
+%%% It has three applications:
+%%% <ol>
+%%% <li>fish. top-level module, has one dependency: `dependsonplugin'.</li>
+%%% <li>dependsonplugin. This depends on some pre-compile actions by the
+%%% plugin. In the test the plugin creates a file `pre.compile' in the
+%%% top-level folder of this application.</li>
+%%% <li>testplugin. This is a plugin application which creates the file.</li>
+%%% </ol>
+
+-module(depplugins_rt).
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+files() ->
+ [
+ {copy, "../../rebar", "rebar"},
+ {copy, "rebar.config", "rebar.config"},
+ {create, "ebin/fish.app", app(fish, [])},
+
+ {create, "deps/dependsonplugin/ebin/dependsonplugin.app",
+ app(dependsonplugin, [])},
+ {copy, "rebar_dependsonplugin.config",
+ "deps/dependsonplugin/rebar.config"},
+ {copy, "testplugin_mod.erl",
+ "deps/testplugin/plugins/testplugin_mod.erl"},
+ {create, "deps/testplugin/ebin/testplugin.app",
+ app(testplugin, [])}
+ ].
+
+run(_Dir) ->
+ ?assertMatch({ok, _}, retest_sh:run("./rebar compile", [])),
+ ?assertEqual(true, filelib:is_regular("deps/dependsonplugin/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 --git a/inttest/depplugins/rebar.config b/inttest/depplugins/rebar.config
new file mode 100644
index 0000000..3a2e34e
--- /dev/null
+++ b/inttest/depplugins/rebar.config
@@ -0,0 +1 @@
+{deps, [dependsonplugin]}.
diff --git a/inttest/depplugins/rebar_dependsonplugin.config b/inttest/depplugins/rebar_dependsonplugin.config
new file mode 100644
index 0000000..df36213
--- /dev/null
+++ b/inttest/depplugins/rebar_dependsonplugin.config
@@ -0,0 +1,2 @@
+{deps, [testplugin]}.
+{plugins, [testplugin_mod]}.
diff --git a/inttest/depplugins/testplugin_mod.erl b/inttest/depplugins/testplugin_mod.erl
new file mode 100644
index 0000000..055bbc7
--- /dev/null
+++ b/inttest/depplugins/testplugin_mod.erl
@@ -0,0 +1,6 @@
+-module(testplugin_mod).
+-compile(export_all).
+
+pre_compile(Config, _) ->
+ ok = file:write_file("pre.compile", <<"Yadda!">>),
+ rebar_log:log(info, "Wrote ~p/pre.compile~n", [rebar_utils:get_cwd()]).
diff --git a/inttest/tdeps1/a.rebar.config b/inttest/tdeps1/a.rebar.config
index 01609ee..991ea5a 100644
--- a/inttest/tdeps1/a.rebar.config
+++ b/inttest/tdeps1/a.rebar.config
@@ -1 +1 @@
-{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
+{deps, [{b, "1", {git, "../repo/b"}}]}.
diff --git a/inttest/tdeps1/b.rebar.config b/inttest/tdeps1/b.rebar.config
index 59c8987..ffbd0db 100644
--- a/inttest/tdeps1/b.rebar.config
+++ b/inttest/tdeps1/b.rebar.config
@@ -1 +1 @@
-{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
+{deps, [{c, "1", {git, "../repo/c"}}]}.
diff --git a/inttest/tdeps1/tdeps1_rt.erl b/inttest/tdeps1/tdeps1_rt.erl
index 43184c6..3de1a2b 100644
--- a/inttest/tdeps1/tdeps1_rt.erl
+++ b/inttest/tdeps1/tdeps1_rt.erl
@@ -23,12 +23,23 @@ files() ->
{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", []),
diff --git a/inttest/tdeps2/a.rebar.config b/inttest/tdeps2/a.rebar.config
index 01609ee..991ea5a 100644
--- a/inttest/tdeps2/a.rebar.config
+++ b/inttest/tdeps2/a.rebar.config
@@ -1 +1 @@
-{deps, [{b, "1", {hg, "../repo/b", "tip"}}]}.
+{deps, [{b, "1", {git, "../repo/b"}}]}.
diff --git a/inttest/tdeps2/b.rebar.config b/inttest/tdeps2/b.rebar.config
index 59c8987..ffbd0db 100644
--- a/inttest/tdeps2/b.rebar.config
+++ b/inttest/tdeps2/b.rebar.config
@@ -1 +1 @@
-{deps, [{c, "1", {hg, "../repo/c", "tip"}}]}.
+{deps, [{c, "1", {git, "../repo/c"}}]}.
diff --git a/inttest/tdeps2/tdeps2_rt.erl b/inttest/tdeps2/tdeps2_rt.erl
index bad546e..987567e 100644
--- a/inttest/tdeps2/tdeps2_rt.erl
+++ b/inttest/tdeps2/tdeps2_rt.erl
@@ -31,12 +31,23 @@ files() ->
{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.
diff --git a/priv/templates/simpleapp_sup.erl b/priv/templates/simpleapp_sup.erl
index 931db75..477479f 100644
--- a/priv/templates/simpleapp_sup.erl
+++ b/priv/templates/simpleapp_sup.erl
@@ -1,4 +1,3 @@
-
-module({{appid}}_sup).
-behaviour(supervisor).
diff --git a/priv/templates/simplenode.erl.script b/priv/templates/simplenode.erl.script
index 6f65e3f..f4c63af 100644
--- a/priv/templates/simplenode.erl.script
+++ b/priv/templates/simplenode.erl.script
@@ -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 --git a/priv/templates/simplenode.nodetool b/priv/templates/simplenode.nodetool
index eb08fa4..ce06c6a 100644..100755
--- a/priv/templates/simplenode.nodetool
+++ b/priv/templates/simplenode.nodetool
@@ -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 @@ main(Args) ->
["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 @@ main(Args) ->
_ ->
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]),
@@ -53,7 +97,7 @@ main(Args) ->
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}\n")
end,
net_kernel:stop().
diff --git a/priv/templates/simplenode.reltool.config b/priv/templates/simplenode.reltool.config
index b580c2a..bac7270 100644
--- a/priv/templates/simplenode.reltool.config
+++ b/priv/templates/simplenode.reltool.config
@@ -1,3 +1,5 @@
+%% -*- mode: erlang -*-
+%% ex: ft=erlang
{sys, [
{lib_dirs, []},
{erts, [{mod_cond, derived}, {app_file, strip}]},
@@ -17,9 +19,9 @@
{boot_rel, "{{nodeid}}"},
{profile, embedded},
{incl_cond, derived},
- {mod_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, {{nodeid}}, [{mod_cond, app}, {incl_cond, include}]}
@@ -31,6 +33,8 @@
{mkdir, "log/sasl"},
{copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
{copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/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"},
diff --git a/priv/templates/simplenode.runner b/priv/templates/simplenode.runner
index 43d90bc..c2ef258 100755
--- a/priv/templates/simplenode.runner
+++ b/priv/templates/simplenode.runner
@@ -2,7 +2,20 @@
# -*- 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
@@ -11,10 +24,17 @@ RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc
# 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
# Identify the script name
@@ -25,7 +45,8 @@ START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
ERTS_VSN=${START_ERL% *}
APP_VSN=${START_ERL#* }
-# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or else etc/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
@@ -54,7 +75,7 @@ else
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
@@ -64,12 +85,13 @@ fi
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
+# 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
@@ -81,7 +103,6 @@ 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
@@ -91,11 +112,35 @@ NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
# 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_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
@@ -122,27 +167,28 @@ case "$1" in
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
@@ -168,7 +214,7 @@ case "$1" in
ping)
## See if the VM is alive
- $NODETOOL ping
+ ping_node
ES=$?
if [ "$ES" -ne 0 ]; then
exit $ES
@@ -176,8 +222,8 @@ case "$1" in
;;
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!"
@@ -189,8 +235,8 @@ case "$1" in
;;
remote_console)
- # 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!"
@@ -210,7 +256,7 @@ case "$1" in
fi
# Make sure a node IS running
- RES=`$NODETOOL ping`
+ ping_node
ES=$?
if [ "$ES" -ne 0 ]; then
echo "Node is not running!"
@@ -283,8 +329,17 @@ case "$1" in
# 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|start_boot <file>|foreground|stop|restart|reboot|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade}"
+ echo "Usage: $SCRIPT {start|start_boot <file>|foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot <file>|attach|remote_console|upgrade}"
exit 1
;;
esac
diff --git a/priv/templates/simplenode.windows.runner.cmd b/priv/templates/simplenode.windows.runner.cmd
index 16da5b9..d45f438 100644
--- a/priv/templates/simplenode.windows.runner.cmd
+++ b/priv/templates/simplenode.windows.runner.cmd
@@ -30,6 +30,7 @@
@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
@@ -38,13 +39,14 @@
@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
@@ -71,6 +73,11 @@
@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
@%erlsrv% list %service_name%
@exit %ERRORLEVEL%
diff --git a/rebar.config.sample b/rebar.config.sample
index a6fb03f..d8d61fc 100644
--- a/rebar.config.sample
+++ b/rebar.config.sample
@@ -156,7 +156,7 @@
{deps, [application_name,
{application_name, "1.0.*"},
{application_name, "1.0.*",
- {git, "git://github.com/basho/rebar.git", {branch, "master"}}},
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}}},
%% Dependencies can be marked as 'raw'. Rebar does not require
%% such dependencies to have a standard Erlang/OTP layout
%% which assumes the presence of either
@@ -171,7 +171,7 @@
%% 'raw' subdirectories: get-deps, update-deps, check-deps,
%% list-deps and delete-deps.
{application_name, "",
- {git, "git://github.com/basho/rebar.git", {branch, "master"}},
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}},
[raw]}]}.
%% == Subdirectories ==
@@ -213,9 +213,9 @@
%% 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\"))",[]}]}.
diff --git a/src/getopt.erl b/src/getopt.erl
index 175b7a5..f9852fb 100644
--- a/src/getopt.erl
+++ b/src/getopt.erl
@@ -11,19 +11,11 @@
-module(getopt).
-author('juanjo@comellas.org').
--export([parse/2, usage/2, usage/3, usage/4]).
+-export([parse/2, usage/2, usage/3, usage/4, tokenize/1]).
+-export([usage_cmd_line/2]).
--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).
+-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).
@@ -33,42 +25,48 @@
-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'.
+-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().
+-type arg_value() :: atom() | binary() | boolean() | float() | integer() | string().
%% Argument specification.
--type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined.
+-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().
+-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
+ Name :: atom(),
+ Short :: char() | undefined,
+ Long :: string() | undefined,
+ ArgSpec :: arg_spec(),
+ Help :: string() | undefined
}.
%% Output streams
--type output_stream() :: 'standard_io' | 'standard_error'.
+-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.
-spec parse([option_spec()], string() | [string()]) ->
- {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}.
-parse(OptSpecList, CmdLine) ->
+ {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}.
+parse(OptSpecList, CmdLine) when is_list(CmdLine) ->
try
Args = if
- is_integer(hd(CmdLine)) ->
- string:tokens(CmdLine, " \t\n");
- true ->
- CmdLine
+ is_integer(hd(CmdLine)) -> tokenize(CmdLine);
+ true -> CmdLine
end,
parse(OptSpecList, [], [], 0, Args)
catch
@@ -78,7 +76,7 @@ parse(OptSpecList, CmdLine) ->
-spec parse([option_spec()], [option()], [string()], integer(), [string()]) ->
- {ok, {[option()], [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.
@@ -110,7 +108,7 @@ parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) ->
%% --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()]}}.
+ {ok, {[option()], [string()]}}.
parse_long_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
case split_assigned_arg(OptArg) of
{Long, Arg} ->
@@ -197,7 +195,7 @@ parse_long_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _Sh
%% -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()]}}.
+ {ok, {[option()], [string()]}}.
parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) ->
parse_short_option(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, first, OptArg).
@@ -258,7 +256,7 @@ parse_short_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, {Name, _S
%% 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};
+ {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) ->
@@ -272,12 +270,12 @@ find_non_option_arg([], _Pos) ->
-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);
+ 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);
@@ -470,152 +468,375 @@ is_non_neg_float_arg([]) ->
%% arguments that are supported by the program.
-spec usage([option_spec()], string()) -> ok.
usage(OptSpecList, ProgramName) ->
- usage(OptSpecList, ProgramName, standard_error).
+ 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)]);
+ 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).
+ 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.
+-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, "Usage: ~s~s ~s~n~n~s~n",
- [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, usage_options(OptSpecList)]);
+ 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).
+ 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.
+-spec usage([option_spec()], ProgramName :: string(), CmdLineTail :: string(),
+ [{OptionName :: string(), Help :: 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]
+ 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;
-add_option_help(_Opt, _Prefix, Acc) ->
- Acc.
+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 HelpLength 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) ->
+ float_to_list(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.
-%% @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;
+-spec line_length() -> non_neg_integer().
+line_length() ->
+ case io:columns() of
+ {ok, Columns} when Columns < ?LINE_LENGTH ->
+ Columns - 1;
_ ->
- T
+ ?LINE_LENGTH
end.
diff --git a/src/rebar.erl b/src/rebar.erl
index c872ade..ded5ebe 100644
--- a/src/rebar.erl
+++ b/src/rebar.erl
@@ -76,8 +76,18 @@ run(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"|_]) ->
+ help();
run(["version"]) ->
ok = load_rebar_app(),
%% Display vsn and build time info
@@ -138,6 +148,16 @@ init_config({Options, _NonOptArgs}) ->
%% 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).
+
run_aux(BaseConfig, Commands) ->
%% Make sure crypto is running
case crypto:start() of
@@ -148,18 +168,10 @@ run_aux(BaseConfig, Commands) ->
%% 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
- 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()),
- BaseConfig2 = rebar_config:set_xconf(BaseConfig1, base_dir, AbsCwd),
+ BaseConfig1 = init_config1(BaseConfig),
%% Process each command, resetting any state between each one
- rebar_core:process_commands(CommandAtoms, BaseConfig2).
+ rebar_core:process_commands(CommandAtoms, BaseConfig1).
%%
%% print help/usage string
@@ -169,7 +181,29 @@ help() ->
getopt:usage(OptSpecList, "rebar",
"[var=value,...] <command,...>",
[{"var=value", "rebar global variables (e.g. force=1)"},
- {"command", "Command to run (e.g. compile)"}]).
+ {"command", "Command to run (e.g. compile)"}]),
+ ?CONSOLE(
+ "Core rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {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
diff --git a/src/rebar_abnfc_compiler.erl b/src/rebar_abnfc_compiler.erl
index 0e6749a..37731b5 100644
--- a/src/rebar_abnfc_compiler.erl
+++ b/src/rebar_abnfc_compiler.erl
@@ -47,6 +47,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -62,11 +65,23 @@ compile(Config, _AppFile) ->
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 --git a/src/rebar_appups.erl b/src/rebar_appups.erl
index 0aeccb6..722f161 100644
--- a/src/rebar_appups.erl
+++ b/src/rebar_appups.erl
@@ -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").
@@ -82,6 +85,13 @@
%% 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]),
diff --git a/src/rebar_asn1_compiler.erl b/src/rebar_asn1_compiler.erl
index d93a2e8..25e3fd3 100644
--- a/src/rebar_asn1_compiler.erl
+++ b/src/rebar_asn1_compiler.erl
@@ -30,6 +30,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -48,6 +51,23 @@ clean(_Config, _AppFile) ->
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 --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index 260cdaf..a0dec30 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -226,6 +226,8 @@ format_warnings(Config, Source, Warnings, Opts) ->
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);
diff --git a/src/rebar_cleaner.erl b/src/rebar_cleaner.erl
index 9ddeb8a..7a762f5 100644
--- a/src/rebar_cleaner.erl
+++ b/src/rebar_cleaner.erl
@@ -28,6 +28,9 @@
-export([clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -37,3 +40,17 @@ clean(Config, _AppFile) ->
%% 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 --git a/src/rebar_core.erl b/src/rebar_core.erl
index 5396dd5..3172d64 100644
--- a/src/rebar_core.erl
+++ b/src/rebar_core.erl
@@ -26,7 +26,7 @@
%% -------------------------------------------------------------------
-module(rebar_core).
--export([process_commands/2]).
+-export([process_commands/2, help/2]).
-include("rebar.hrl").
@@ -34,6 +34,35 @@
%% Internal functions
%% ===================================================================
+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).
+
process_commands([], ParentConfig) ->
AbortTrapped = rebar_config:get_xconf(ParentConfig, abort_trapped, false),
case {get_operations(ParentConfig), AbortTrapped} of
@@ -110,21 +139,21 @@ process_dir(Dir, ParentConfig, Command, DirSet) ->
{ok, AvailModuleSets} = application:get_env(rebar, modules),
ModuleSet = choose_module_set(AvailModuleSets, Dir),
skip_or_process_dir(ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet)
+ Dir, Command, DirSet)
end.
skip_or_process_dir({[], undefined}=ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
process_dir1(Dir, Command, DirSet, Config, CurrentCodePath, ModuleSet);
skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
case lists:suffix(".app.src", ModuleSetFile)
orelse lists:suffix(".app", ModuleSetFile) of
true ->
%% .app or .app.src file, check if is_skipped_app
skip_or_process_dir1(ModuleSetFile, ModuleSet,
- Config, CurrentCodePath, Dir,
- Command, DirSet);
+ Config, CurrentCodePath, Dir,
+ Command, DirSet);
false ->
%% not an app dir, no need to consider apps=/skip_apps=
process_dir1(Dir, Command, DirSet, Config,
@@ -132,7 +161,7 @@ skip_or_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
end.
skip_or_process_dir1(AppFile, ModuleSet, Config, CurrentCodePath,
- Dir, Command, DirSet) ->
+ Dir, Command, DirSet) ->
case rebar_app_utils:is_skipped_app(Config, AppFile) of
{Config1, {true, SkippedApp}} ->
?DEBUG("Skipping app: ~p~n", [SkippedApp]),
@@ -158,11 +187,12 @@ process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
{Config1, Predirs} = acc_modules(Modules, preprocess, Config0,
ModuleSetFile),
- 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(Config1, SubdirAssoc),
+ {ok, PluginModules} = plugin_modules(Config1, PredirsAssoc),
{Config2, PluginPredirs} = acc_modules(PluginModules, preprocess,
Config1, ModuleSetFile),
@@ -224,7 +254,7 @@ process_dir1(Dir, Command, DirSet, Config0, CurrentCodePath,
%% Return the updated {config, dirset} as result
Res.
-remember_cwd_subdir(Cwd, Subdirs) ->
+remember_cwd_predirs(Cwd, Predirs) ->
Store = fun(Dir, Dict) ->
case dict:find(Dir, Dict) of
error ->
@@ -235,11 +265,10 @@ remember_cwd_subdir(Cwd, Subdirs) ->
?ABORT("Internal consistency assertion failed.~n"
"sub_dir ~s already associated with ~s.~n"
"Duplicate sub_dirs or deps entries?",
- [Dir, Existing]),
- Dict
+ [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
@@ -459,9 +488,9 @@ acc_modules([Module | Rest], Command, Config, File, Acc) ->
%%
%% 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)).
+ plugin_modules(Config, PredirsAssoc, ulist(Modules)).
ulist(L) ->
ulist(L, []).
@@ -476,16 +505,16 @@ ulist([H | T], Acc) ->
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,
+plugin_modules(Config, PredirsAssoc, FoundModules, MissingModules) ->
+ {Loaded, NotLoaded} = load_plugin_modules(Config, PredirsAssoc,
MissingModules),
AllViablePlugins = FoundModules ++ Loaded,
case NotLoaded =/= [] of
@@ -499,37 +528,43 @@ plugin_modules(Config, SubdirAssoc, FoundModules, MissingModules) ->
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 = 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.
+ [
+ filename:join(Dir, "plugins") ||
+ Dir <- get_plugin_base_dirs(Cwd, PredirsAssoc)
+ ],
%% 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),
%% 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.
+%% @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 --git a/src/rebar_ct.erl b/src/rebar_ct.erl
index 749d025..9951f8e 100644
--- a/src/rebar_ct.erl
+++ b/src/rebar_ct.erl
@@ -39,6 +39,9 @@
-export([ct/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -53,6 +56,26 @@ ct(Config, File) ->
%% ===================================================================
%% Internal functions
%% ===================================================================
+
+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=foo,bar - run <test>/foo_SUITE and <test>/bar_SUITE~n"
+ " case=\"mycase\" - run individual test case foo_SUITE:mycase~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 ->
@@ -85,8 +108,16 @@ run_test(TestDir, LogDir, Config, _File) ->
" 2>&1 | tee -a " ++ RawLog
end,
- rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]),
- check_log(Config, RawLog).
+ case rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}, return_on_error]) 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(LogDir, RawLog) ->
case filelib:ensure_dir(filename:join(LogDir, "index.html")) of
@@ -101,7 +132,16 @@ clear_log(LogDir, RawLog) ->
%% calling ct with erl does not return non-zero on failure - have to check
%% log results
-check_log(Config, RawLog) ->
+check_success_log(Config, RawLog) ->
+ check_log(Config, RawLog, fun(Msg) -> ?CONSOLE("DONE.\n~s\n", [Msg]) end).
+
+check_fail_log(Config, RawLog, Command, {Rc, Output}) ->
+ check_log(Config, RawLog, fun(_Msg) ->
+ ?ABORT("~s failed with error: ~w and output:~n~s~n",
+ [Command, Rc, Output])
+ end).
+
+check_log(Config,RawLog,Fun) ->
{ok, Msg} =
rebar_utils:sh("grep -e 'TEST COMPLETE' -e '{error,make_failed}' "
++ RawLog, [{use_stdout, false}]),
@@ -119,9 +159,10 @@ check_log(Config, RawLog) ->
?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(Config, RawLog) ->
?CONSOLE("Showing log\n", []),
diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl
index 074e929..9f9fc5d 100644
--- a/src/rebar_deps.erl
+++ b/src/rebar_deps.erl
@@ -38,6 +38,8 @@
'delete-deps'/2,
'list-deps'/2]).
+%% for internal use only
+-export([info/2]).
-record(dep, { dir,
app,
@@ -203,12 +205,47 @@ do_check_deps(Config) ->
%% 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, [application_name,
+ {application_name, "1.0.*"},
+ {application_name, "1.0.*",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}}},
+ {application_name, "",
+ {git, "git://github.com/rebar/rebar.git", {branch, "master"}},
+ [raw]}]}
+ ]).
+
%% 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_shared_deps_dir(Config, []) ->
- GlobalDepsDir = rebar_config:get_global(Config, deps_dir, "deps"),
- DepsDir = rebar_config:get_local(Config, deps_dir, GlobalDepsDir),
+ LocalDepsDir = rebar_config:get_local(Config, deps_dir, "deps"),
+ DepsDir = rebar_config:get_global(Config, deps_dir, LocalDepsDir),
rebar_config:set_xconf(Config, deps_dir, DepsDir);
set_shared_deps_dir(Config, _DepsDir) ->
Config.
diff --git a/src/rebar_dia_compiler.erl b/src/rebar_dia_compiler.erl
index 51c075b..f81c734 100644
--- a/src/rebar_dia_compiler.erl
+++ b/src/rebar_dia_compiler.erl
@@ -28,6 +28,9 @@
-export([compile/2, clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -46,6 +49,23 @@ clean(_Config, _AppFile) ->
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, []} (see diameter_codegen:from_dict/4 documentation)~n",
+ [Description]).
+
-spec compile_dia(file:filename(), file:filename(),
rebar_config:config()) -> ok.
compile_dia(Source, Target, Config) ->
diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl
index cf0239c..c828d27 100644
--- a/src/rebar_edoc.erl
+++ b/src/rebar_edoc.erl
@@ -39,6 +39,9 @@
-export([doc/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -71,6 +74,14 @@ doc(Config, File) ->
%% 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
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 91f8354..caef0d2 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -29,8 +29,9 @@
-export([compile/2,
clean/2]).
-%% for internal use by only eunit and qc
--export([test_compile/3]).
+%% for internal use only
+-export([test_compile/3,
+ info/2]).
-include("rebar.hrl").
@@ -116,8 +117,6 @@ clean(_Config, _AppFile) ->
test_compile(Config, Cmd, OutDir) ->
%% Obtain all the test modules for inclusion in the compile stage.
- %% Notice: this could also be achieved with the following
- %% rebar.config option: {test_compile_opts, [{src_dirs, ["src", "test"]}]}
TestErls = rebar_utils:find_files("test", ".*\\.erl\$"),
%% Copy source files to eunit dir for cover in case they are not directly
@@ -165,6 +164,42 @@ test_compile(Config, Cmd, OutDir) ->
%% Internal functions
%% ===================================================================
+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, ["mymib1", "mymib2"]},
+ {mib_opts, []},
+ {mib_first_files, []},
+ {xrl_opts, []},
+ {xrl_first_files, []},
+ {yrl_opts, []},
+ {yrl_first_files, []}
+ ]).
+
test_compile_config(Config, Cmd) ->
{Config1, TriqOpts} = triq_opts(Config),
{Config2, PropErOpts} = proper_opts(Config1),
diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl
index b5fe899..4449be6 100644
--- a/src/rebar_erlydtl_compiler.erl
+++ b/src/rebar_erlydtl_compiler.erl
@@ -96,6 +96,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -123,23 +126,39 @@ compile(Config, _AppFile) ->
true = code:set_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) ->
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: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)
+ 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) ->
diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl
index 706cf7c..0cc43ef 100644
--- a/src/rebar_escripter.erl
+++ b/src/rebar_escripter.erl
@@ -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").
@@ -108,6 +111,30 @@ clean(Config0, AppFile) ->
%% 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) ->
diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl
index ebf2ee4..c997842 100644
--- a/src/rebar_eunit.erl
+++ b/src/rebar_eunit.erl
@@ -61,7 +61,7 @@
%% Additionally, for projects that have separate folders for the core
%% implementation, and for the unit tests, then the following
%% <code>rebar.config</code> option can be provided:
-%% <code>{test_compile_opts, [{src_dirs, ["dir"]}]}.</code>.
+%% <code>{eunit_compile_opts, [{src_dirs, ["src", "dir"]}]}.</code>.
%% @copyright 2009, 2010 Dave Smith
%% -------------------------------------------------------------------
-module(rebar_eunit).
@@ -69,6 +69,9 @@
-export([eunit/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(EUNIT_DIR, ".eunit").
@@ -100,6 +103,40 @@ clean(_Config, _File) ->
%% Internal functions
%% ===================================================================
+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"
+ " suites=\"foo,bar\" (Run tests in foo.erl, test/foo_tests.erl and~n"
+ " tests in bar.erl, test/bar_tests.erl)~n"
+ " tests=\"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",
+ [
+ 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
@@ -186,7 +223,7 @@ filter_suites(Config, Modules) ->
filter_suites1(Modules, []) ->
Modules;
filter_suites1(Modules, Suites) ->
- [M || M <- Modules, lists:member(M, Suites)].
+ [M || M <- Suites, lists:member(M, Modules)].
%%
%% == get matching tests ==
@@ -763,7 +800,6 @@ 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
diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl
index d288ca5..2a047d8 100644
--- a/src/rebar_lfe_compiler.erl
+++ b/src/rebar_lfe_compiler.erl
@@ -30,6 +30,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -45,6 +48,14 @@ compile(Config, _AppFile) ->
%% 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 ->
diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl
index 33f32e3..b9f23f2 100644
--- a/src/rebar_neotoma_compiler.erl
+++ b/src/rebar_neotoma_compiler.erl
@@ -42,6 +42,9 @@
-export([compile/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ============================================================================
@@ -53,13 +56,26 @@ compile(Config, _AppFile) ->
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}]).
%% ============================================================================
%% Internal functions
%% ============================================================================
+info(help, compile) ->
+ ?CONSOLE(
+ "Build Neotoma (*.peg) sources.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n",
+ [
+ {neotom_opts, [{doc_root, "src"},
+ {out_dir, "src"},
+ {source_ext, ".peg"},
+ {module_ext, ""}]}
+ ]).
+
neotoma_opts(Config) ->
rebar_config:get(Config, neotoma_opts, []).
diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl
index a62f584..b3566c8 100644
--- a/src/rebar_otp_app.erl
+++ b/src/rebar_otp_app.erl
@@ -29,6 +29,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -82,11 +85,26 @@ clean(_Config, File) ->
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(Config, AppSrcFile) of
{ok, Config1, AppName, AppData} ->
@@ -101,8 +119,12 @@ preprocess(Config, 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),
@@ -187,3 +209,12 @@ validate_modules(AppName, Mods) ->
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 --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl
index 06a79bc..0abb044 100644
--- a/src/rebar_port_compiler.erl
+++ b/src/rebar_port_compiler.erl
@@ -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").
@@ -97,7 +100,8 @@ compile(Config, AppFile) ->
[] ->
ok;
Specs ->
- SharedEnv = rebar_config:get_env(Config, ?MODULE),
+ SharedEnv = rebar_config:get_env(Config, rebar_deps) ++
+ rebar_config:get_env(Config, ?MODULE),
%% Compile each of the sources
NewBins = compile_sources(Config, Specs, SharedEnv),
@@ -149,6 +153,27 @@ setup_env(Config) ->
%% 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
diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl
index 7ef58d6..579ecfb 100644
--- a/src/rebar_protobuffs_compiler.erl
+++ b/src/rebar_protobuffs_compiler.erl
@@ -29,6 +29,9 @@
-export([compile/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
%% ===================================================================
@@ -57,7 +60,6 @@ compile(Config, _AppFile) ->
end
end.
-
clean(_Config, _AppFile) ->
%% Get a list of generated .beam and .hrl files and then delete them
Protos = rebar_utils:find_files("src", ".*\\.proto$"),
@@ -71,11 +73,24 @@ clean(_Config, _AppFile) ->
delete_each(Targets)
end.
-
%% ===================================================================
%% Internal functions
%% ===================================================================
+info(help, compile) ->
+ info_help("Build Protobuffs (*.proto) sources");
+info(help, clean) ->
+ info_help("Delete Protobuffs (*.proto) build results").
+
+info_help(Description) ->
+ ?CONSOLE(
+ "~s.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " erl_opts is passed as compile_flags to "
+ "protobuffs_compile:scan_file/2~n",
+ [Description]).
+
protobuffs_is_present() ->
code:which(protobuffs_compile) =/= non_existing.
@@ -115,7 +130,7 @@ compile_each(Config, [{Proto, Beam, Hrl} | Rest]) ->
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;
diff --git a/src/rebar_qc.erl b/src/rebar_qc.erl
index 09e48e4..53a6f52 100644
--- a/src/rebar_qc.erl
+++ b/src/rebar_qc.erl
@@ -28,6 +28,9 @@
-export([qc/2, triq/2, eqc/2, clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(QC_DIR, ".qc").
@@ -57,6 +60,19 @@ clean(_Config, _File) ->
%% 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",
+ [
+ {qc_compile_opts, []},
+ {qc_first_files, []}
+ ]).
+
-define(TRIQ_MOD, triq).
-define(EQC_MOD, eqc).
@@ -159,6 +175,7 @@ qc_module(QC=triq, _QCOpts, M) ->
Failed ->
[Failed]
end;
+qc_module(QC=eqc, [], M) -> QC:module(M);
qc_module(QC=eqc, QCOpts, M) -> QC:module(QCOpts, M).
find_prop_mods() ->
diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl
index 3c9b728..9f9488e 100644
--- a/src/rebar_reltool.erl
+++ b/src/rebar_reltool.erl
@@ -30,6 +30,9 @@
overlay/2,
clean/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-include_lib("kernel/include/file.hrl").
@@ -80,6 +83,32 @@ clean(Config, ReltoolFile) ->
%% 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.
@@ -137,22 +166,32 @@ process_overlay(Config, ReltoolConfig) ->
%% providing an additional file on the command-line.
%%
overlay_vars(Config, Vars0, ReltoolConfig) ->
- BaseVars = load_vars_file(proplists:get_value(overlay_vars, ReltoolConfig)),
- OverrideVars = load_vars_file(rebar_config:get_global(Config,
- overlay_vars,
- undefined)),
- M = fun(_Key, _Base, Override) -> Override end,
+ 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) ->
+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 ~p: ~p\n", [File, Reason])
end.
diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl
index 9a0a005..385f55c 100644
--- a/src/rebar_require_vsn.erl
+++ b/src/rebar_require_vsn.erl
@@ -33,6 +33,9 @@
-export([compile/2,
eunit/2]).
+%% for internal use only
+-export([info/2]).
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -47,6 +50,25 @@ eunit(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) ->
ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"),
ReOpts = [{capture, none}],
diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl
index 0e1eef1..e997975 100644
--- a/src/rebar_templater.erl
+++ b/src/rebar_templater.erl
@@ -35,6 +35,9 @@
-export([resolve_variables/2,
render/2]).
+%% for internal use only
+-export([info/2]).
+
-include("rebar.hrl").
-define(TEMPLATE_RE, ".*\\.template\$").
@@ -81,6 +84,9 @@ resolve_variables([], 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).
@@ -98,6 +104,27 @@ render(Bin, Context) ->
%% 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-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]),
@@ -134,14 +161,14 @@ create1(Config, TemplateId) ->
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,
@@ -275,7 +302,7 @@ consult(Cont, Str, Acc) ->
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, _} ->
@@ -286,6 +313,13 @@ consult(Cont, Str, Acc) ->
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),
diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl
index 14ea758..d18603c 100644
--- a/src/rebar_upgrade.erl
+++ b/src/rebar_upgrade.erl
@@ -32,6 +32,9 @@
-export(['generate-upgrade'/2]).
+%% for internal use only
+-export([info/2]).
+
-define(TMP, "_tmp").
%% ====================================================================
@@ -80,6 +83,13 @@
%% Internal functions
%% ==================================================================
+info(help, 'generate-upgrade') ->
+ ?CONSOLE("Build an upgrade package.~n"
+ "~n"
+ "Valid command line options:~n"
+ " previous_release=path~n",
+ []).
+
run_checks(Config, OldVerPath, ReltoolConfig) ->
true = rebar_utils:prop_check(filelib:is_dir(OldVerPath),
"Release directory doesn't exist (~p)~n",
diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl
index bb58460..fd93f98 100644
--- a/src/rebar_utils.erl
+++ b/src/rebar_utils.erl
@@ -195,7 +195,7 @@ expand_env_variable(InStr, VarName, RawVarValue) ->
%% 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}],
+ ReOpts = [global, {return, list}, unicode],
re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts)
end.
diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl
index a55d71d..8c0a872 100644
--- a/src/rebar_xref.erl
+++ b/src/rebar_xref.erl
@@ -37,6 +37,9 @@
-export([xref/2]).
+%% for internal use only
+-export([info/2]).
+
%% ===================================================================
%% Public API
%% ===================================================================
@@ -100,6 +103,22 @@ xref(Config, _) ->
%% Internal functions
%% ===================================================================
+info(help, xref) ->
+ ?CONSOLE(
+ "Run cross reference analysis.~n"
+ "~n"
+ "Valid rebar.config options:~n"
+ " ~p~n"
+ " ~p~n"
+ " ~p~n",
+ [
+ {xref_warnings, false},
+ {xref_checks, [exports_not_used, undefined_function_calls]},
+ {xref_queries,
+ [{"(xc - uc) || (xu - x - b"
+ " - (\"mod\":\".*foo\"/\"4\"))",[]}]}
+ ]).
+
check_exports_not_used() ->
{ok, UnusedExports0} = xref:analyze(xref, exports_not_used),
UnusedExports = filter_away_ignored(UnusedExports0),
diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl
index 7b2eec5..61a9bbf 100644
--- a/test/rebar_eunit_tests.erl
+++ b/test/rebar_eunit_tests.erl
@@ -337,7 +337,15 @@ basic_setup_test_() ->
"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(),
@@ -389,8 +397,8 @@ prepare_rebar_script() ->
{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() ->
diff --git a/test/upgrade_project/rel/files/dummy b/test/upgrade_project/rel/files/dummy
index cfbfd4f..c2ef258 100755
--- a/test/upgrade_project/rel/files/dummy
+++ b/test/upgrade_project/rel/files/dummy
@@ -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 @@ START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data`
ERTS_VSN=${START_ERL% *}
APP_VSN=${START_ERL#* }
-# Use releases/VSN/vm.args if it exists otherwise use etc/vm.args
-if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then
- VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args"
+# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or
+# else etc/vm.args
+if [ -e "$CALLER_DIR/vm.args" ]; then
+ VMARGS_PATH=$CALLER_DIR/vm.args
+ USE_DIR=$CALLER_DIR
else
- VMARGS_PATH="$RUNNER_ETC_DIR/vm.args"
+ USE_DIR=$RUNNER_BASE_DIR
+ if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then
+ VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args"
+ else
+ VMARGS_PATH="$RUNNER_ETC_DIR/vm.args"
+ fi
fi
+RUNNER_LOG_DIR=$USE_DIR/log
+# Make sure log directory exists
+mkdir -p $RUNNER_LOG_DIR
+
# Use releases/VSN/sys.config if it exists otherwise use etc/app.config
-if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then
- CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config"
+if [ -e "$USE_DIR/sys.config" ]; then
+ CONFIG_PATH="$USE_DIR/sys.config"
else
- CONFIG_PATH="$RUNNER_ETC_DIR/app.config"
+ if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then
+ CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config"
+ else
+ CONFIG_PATH="$RUNNER_ETC_DIR/app.config"
+ fi
fi
# Extract the target node name from node.args
-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 @@ fi
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 @@ NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
# 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 @@ case "$1" in
ping)
## See if the VM is alive
- $NODETOOL ping
+ ping_node
ES=$?
if [ "$ES" -ne 0 ]; then
exit $ES
@@ -148,8 +222,8 @@ case "$1" in
;;
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!"
@@ -161,8 +235,8 @@ case "$1" in
;;
remote_console)
- # 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!"
@@ -182,7 +256,7 @@ case "$1" in
fi
# Make sure a node IS running
- RES=`$NODETOOL ping`
+ ping_node
ES=$?
if [ "$ES" -ne 0 ]; then
echo "Node is not running!"
@@ -195,12 +269,18 @@ case "$1" in
$ERTS_PATH/escript $RUNNER_BASE_DIR/bin/install_upgrade.escript $node_name $erlang_cookie $2
;;
- console|console_clean)
+ console|console_clean|console_boot)
# .boot file typically just $SCRIPT (ie, the app name)
- # however, for debugging, sometimes start_clean.boot is useful:
+ # however, for debugging, sometimes start_clean.boot is useful.
+ # For e.g. 'setup', one may even want to name another boot script.
case "$1" in
console) BOOTFILE=$SCRIPT ;;
console_clean) BOOTFILE=start_clean ;;
+ console_boot)
+ shift
+ BOOTFILE="$1"
+ shift
+ ;;
esac
# Setup beam-required vars
ROOTDIR=$RUNNER_BASE_DIR
@@ -249,8 +329,17 @@ case "$1" in
# 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 <file>|foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot <file>|attach|remote_console|upgrade}"
exit 1
;;
esac
diff --git a/test/upgrade_project/rel/files/erl b/test/upgrade_project/rel/files/erl
index 6f65e3f..f4c63af 100755
--- a/test/upgrade_project/rel/files/erl
+++ b/test/upgrade_project/rel/files/erl
@@ -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 --git a/test/upgrade_project/rel/files/nodetool b/test/upgrade_project/rel/files/nodetool
index eb08fa4..ce06c6a 100644
--- a/test/upgrade_project/rel/files/nodetool
+++ b/test/upgrade_project/rel/files/nodetool
@@ -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 @@ main(Args) ->
["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 @@ main(Args) ->
_ ->
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]),
@@ -53,7 +97,7 @@ main(Args) ->
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}\n")
end,
net_kernel:stop().
diff --git a/test/upgrade_project/rel/files/sys.config b/test/upgrade_project/rel/files/sys.config
index d7677e7..3b7f6bd 100644
--- a/test/upgrade_project/rel/files/sys.config
+++ b/test/upgrade_project/rel/files/sys.config
@@ -8,3 +8,4 @@
{error_logger_mf_maxfiles, 5} % 5 files max
]}
].
+
diff --git a/test/upgrade_project/rel/files/vm.args b/test/upgrade_project/rel/files/vm.args
index 2d64e25..a9aeb64 100644
--- a/test/upgrade_project/rel/files/vm.args
+++ b/test/upgrade_project/rel/files/vm.args
@@ -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