diff options
64 files changed, 2831 insertions, 1007 deletions
diff --git a/HACKING b/HACKING deleted file mode 100644 index 6df34c5..0000000 --- a/HACKING +++ /dev/null @@ -1,50 +0,0 @@ -Indenting -========= -To have consistent indenting we have vi modeline/emacs local variable -headers in rebar's source files. This works automatically with vi. -With Emacs you have to declare 'erlang-indent-level set to 4' -as a safe local variable value. If not configured Emacs will prompt -you to save this as part of custom-set-variables: - '(safe-local-variable-values (quote ((erlang-indent-level . 4)))) -You can also tell Emacs to ignore file variables: -(setq enable-local-variables nil - enable-local-eval nil) - - -Writing Commit Messages -======================= - - One line summary (< 50 characters) - - Longer description (wrap at 72 characters) - -Summary -------- - -* Less than 50 characters - -* What was changed - -* Imperative present tense (fix, add, change) - - Fix bug 42 - Add 'foobar' command - Change default timeout to 42 - -* No period - -Description ------------ - -* Wrap at 72 characters - -* Why, explain intention and implementation approach - -* Present tense - -Atomicity ---------- - -* Break up logical changes - -* Make whitespace changes separately diff --git a/README.md b/README.md new file mode 100644 index 0000000..b683df1 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +rebar +===== + +rebar is an Erlang build tool that makes it easy to compile and +test Erlang applications, port drivers and releases. + +rebar is a self-contained Erlang script, so it's easy to distribute or even +embed directly in a project. Where possible, rebar uses standard Erlang/OTP +conventions for project structures, thus minimizing the amount of build +configuration work. rebar also provides dependency management, enabling +application writers to easily re-use common libraries from a variety of +locations (git, hg, etc). + +Building +-------- + +Information on building and installing Erlang/OTP can be found +in the `INSTALL.md` document. + +### Dependencies + +To build rebar you will need a working installation of Erlang R13B03 (or +later). + +Should you want to clone the rebar repository, you will also require git. + +#### Downloading + +Clone the git repository: + + $ git clone git://github.com/basho/rebar.git + +#### Building rebar + + $ cd rebar/ + $ ./bootstrap + Recompile: src/getopt + ... + Recompile: src/rebar_utils + ==> rebar (compile) + Congratulations! You now have a self-contained script called "rebar" in + your current working directory. Place this script anywhere in your path + and you can use rebar to build OTP-compliant apps. + + +Contributing to rebar +===================== + +Coding style +------------ + +Do not introduce trailing whitespace. + +Do not introduce lines longer than 80 characters. + +### Indentation + +To have consistent indentation we have vi modeline/emacs local variable +headers in rebar's source files. This works automatically with vi. +With Emacs you have to declare <code>'erlang-indent-level</code> +set to <code>4</code> +as a safe local variable value. If not configured Emacs will prompt +you to save this as part of custom-set-variables: + + '(safe-local-variable-values (quote ((erlang-indent-level . 4)))) +You can also tell Emacs to ignore file variables: + + (setq enable-local-variables nil + enable-local-eval nil) + + +Writing Commit Messages +----------------------- + +Structure your commit message like this: + +<pre> +One line summary (less than 50 characters) + +Longer description (wrap at 72 characters) +</pre> + +### Summary + +* Less than 50 characters +* What was changed +* Imperative present tense (fix, add, change) +> Fix bug 123 +> Add 'foobar' command +> Change default timeout to 123 +* No period + +### Description + +* Wrap at 72 characters +* Why, explain intention and implementation approach +* Present tense + +### Atomicity + +* Break up logical changes +* Make whitespace changes separately + +Dialyzer and Tidier +------------------- + +Before you submit a patch check for discrepancies with +[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html): + +<pre> +$ cd rebar/ +$ ./bootstrap debug +$ dialyzer ebin -Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs +</pre> + +The following discrepancies are known and safe to ignore: +<pre> +rebar_templater.erl:249: The call rebar_templater:consult( + Cont1::erl_scan:return_cont(),'eof', + Acc::[any()]) + contains an opaque term as 1st argument when terms + of different types are expected in these positions +rebar_utils.erl:144: Call to missing or unexported function escript:foldl/3 +rebar_utils.erl:165: The created fun has no local return +</pre> + +It is **strongly recommended** to check the code with +[Tidier](http://tidier.softlab.ntua.gr:20000/tidier/getstarted). +Select all transformation options and enable **automatic** +transformation. +If Tidier suggests a transformation apply the changes **manually** +to the source code. +Do not use the code from the tarball (*out.tgz*) as it will have +white-space changes +applied by Erlang's pretty-printer. @@ -17,7 +17,7 @@ Chris Bernard Jeremy Raymond Bob Ippolito Alex Songe -Vagabond +Andrew Thompson Russell Brown Chris Chew Klas Johansson @@ -34,3 +34,12 @@ Matthew Batema Alexey Romanov Benjamin Nortier Magnus Klaar +Anthony Ramine +Charles McKnight +Andrew Tunnell-Jones +Joe Williams +Daniel Reverri +Jesper Louis Andersen +Richard Jones +Tim Watson +Anders 'andekar' @@ -1,2 +0,0 @@ -* write documentation -* ZSH completion script @@ -1,5 +1,5 @@ #!/usr/bin/env escript -%% -*- mode:erlang;tab-width:4;erlang-indent-level:4;indent-tabs-mode:nil -*- +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et main(Args) -> @@ -19,12 +19,10 @@ main(Args) -> end, %% Add check for debug flag - case lists:member("debug", Args) of - true -> - DebugFlag = debug_info; - false -> - DebugFlag = undefined - end, + DebugFlag = case lists:member("debug", Args) of + true -> debug_info; + false -> undefined + end, %% Compile all src/*.erl to ebin case make:files(filelib:wildcard("src/*.erl"), [{outdir, "ebin"}, {i, "include"}, diff --git a/ebin/rebar.app b/ebin/rebar.app index 6f9b6f5..a99df4e 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -2,6 +2,8 @@ [{description, "Rebar: Erlang Build Tool"}, {vsn, "2"}, {modules, [ rebar, + rebar_abnfc_compiler, + rebar_appups, rebar_app_utils, rebar_base_compiler, rebar_config, @@ -31,6 +33,7 @@ rebar_require_vsn, rebar_subdirs, rebar_templater, + rebar_upgrade, rebar_utils, rebar_xref, getopt, @@ -59,6 +62,7 @@ {modules, [ {app_dir, [ rebar_pre_script, + rebar_abnfc_compiler, rebar_protobuffs_compiler, rebar_neotoma_compiler, rebar_asn1_compiler, @@ -77,7 +81,9 @@ ]}, {rel_dir, [ - rebar_reltool + rebar_appups, + rebar_reltool, + rebar_upgrade ]} ]} ]} diff --git a/inttest/rgen1/reltool.config b/inttest/rgen1/reltool.config index c6e31f5..4c4713c 100644 --- a/inttest/rgen1/reltool.config +++ b/inttest/rgen1/reltool.config @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et {sys, [ {rel, "rgen1", "0.1", diff --git a/inttest/tdeps1/tdeps1_rt.erl b/inttest/tdeps1/tdeps1_rt.erl index 5eb831d..9072e9c 100644 --- a/inttest/tdeps1/tdeps1_rt.erl +++ b/inttest/tdeps1/tdeps1_rt.erl @@ -23,7 +23,7 @@ files() -> {copy, "c.hrl", "repo/c/include/c.hrl"} ]. -run(Dir) -> +run(_Dir) -> %% Initialize the b/c apps as mercurial repos so that dependencies pull %% properly HgCmd = "/bin/sh -c \"hg init && hg add && hg commit -m 'Initial commit'\"", @@ -33,7 +33,7 @@ run(Dir) -> {ok, _} = retest_sh:run("./rebar get-deps compile", []), - true = filelib:is_file("ebin/a.beam"), + true = filelib:is_regular("ebin/a.beam"), ok. diff --git a/priv/shell-completion/bash/rebar b/priv/shell-completion/bash/rebar index 237255c..581ec0d 100644 --- a/priv/shell-completion/bash/rebar +++ b/priv/shell-completion/bash/rebar @@ -10,9 +10,9 @@ _rebar() lopts=" --help --commands --verbose --force --jobs= --version" cmdsnvars="build-plt check-plt check-deps clean compile \ create create-app create-node ct dialyze doc delete-deps eunit \ - get-deps generate help list-templates version xref \ - case= debug_info=1 force=1 jobs= suite= verbose=1 appid= \ - skip_deps=1 template= template_dir=" + get-deps generate generate-upgrade help list-templates update-deps \ + version xref case= debug_info=1 force=1 jobs= suite= verbose=1 \ + appid= previous_release= skip_deps=1 template= template_dir=" if [[ ${cur} == --* ]] ; then COMPREPLY=( $(compgen -W "${lopts}" -- ${cur}) ) diff --git a/priv/templates/ctsuite.erl b/priv/templates/ctsuite.erl new file mode 100644 index 0000000..33a8fac --- /dev/null +++ b/priv/templates/ctsuite.erl @@ -0,0 +1,167 @@ +%% common_test suite for {{testmod}} + +-module({{testmod}}_SUITE). +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> [{timetrap, {seconds, 20}}]. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% The name of the group. +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% Group properties that may be combined. +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% The name of a test case. +%% Shuffle = shuffle | {shuffle,Seed} +%% To get cases executed in random order. +%% Seed = {integer(),integer(),integer()} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% To get execution of cases repeated. +%% N = integer() | forever +%% +%% Description: Returns a list of test case group definitions. +%%-------------------------------------------------------------------- +groups() -> []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases +%% +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% Name of a test case group. +%% TestCase = atom() +%% Name of a test case. +%% +%% Description: Returns the list of groups and test cases that +%% are to be executed. +%% +%% NB: By default, we export all 1-arity user defined functions +%%-------------------------------------------------------------------- +all() -> + [ {exports, Functions} | _ ] = ?MODULE:module_info(), + [ FName || {FName, _} <- lists:filter( + fun ({module_info,_}) -> false; + ({all,_}) -> false; + ({init_per_suite,1}) -> false; + ({end_per_suite,1}) -> false; + ({_,1}) -> true; + ({_,_}) -> false + end, Functions)]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% Reason = term() +%% The reason for skipping all test cases and subgroups in the group. +%% +%% Description: Initialization before each test case group. +%%-------------------------------------------------------------------- +init_per_group(_group, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% +%% Description: Cleanup after each test case group. +%%-------------------------------------------------------------------- +end_per_group(_group, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for failing the test case. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(TestCase, Config) -> + Config. + +test_{{testmod}}() -> + [{userdata,[{doc,"Testing the {{testmod}} module"}]}]. + +test_{{testmod}}(_Config) -> + {skip,"Not implemented."}. diff --git a/priv/templates/ctsuite.template b/priv/templates/ctsuite.template new file mode 100644 index 0000000..b7de337 --- /dev/null +++ b/priv/templates/ctsuite.template @@ -0,0 +1,2 @@ +{variables, [{testmod, "mymodule"}]}. +{template, "ctsuite.erl", "test/{{testmod}}_SUITE.erl"}. diff --git a/priv/templates/simplenode.nodetool b/priv/templates/simplenode.nodetool index 971f983..eb08fa4 100644 --- a/priv/templates/simplenode.nodetool +++ b/priv/templates/simplenode.nodetool @@ -1,4 +1,5 @@ -%% -*- erlang -*- +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et %% ------------------------------------------------------------------- %% %% nodetool: Helper Script for interacting with live nodes @@ -6,6 +7,7 @@ %% ------------------------------------------------------------------- main(Args) -> + ok = start_epmd(), %% Extract the args {RestArgs, TargetNode} = process_args(Args, [], undefined), @@ -30,7 +32,8 @@ 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), [RpcArgs], 60000) of + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + [RpcArgs], 60000) of ok -> ok; {badrpc, Reason} -> @@ -39,6 +42,15 @@ main(Args) -> _ -> halt(1) end; + ["rpcterms", Module, Function, ArgsAsString] -> + 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]), + halt(1); + Other -> + io:format("~p\n", [Other]) + end; Other -> io:format("Other: ~p\n", [Other]), io:format("Usage: nodetool {ping|stop|restart|reboot}\n") @@ -62,6 +74,27 @@ process_args([Arg | Rest], Acc, Opts) -> process_args(Rest, [Arg | Acc], Opts). +start_epmd() -> + [] = os:cmd(epmd_path() ++ " -daemon"), + ok. + +epmd_path() -> + ErtsBinDir = filename:dirname(escript:script_name()), + Name = "epmd", + case os:find_executable(Name, ErtsBinDir) of + false -> + case os:find_executable(Name) of + false -> + io:format("Could not find epmd.~n"), + halt(1); + GlobalEpmd -> + GlobalEpmd + end; + Epmd -> + Epmd + end. + + nodename(Name) -> case string:tokens(Name, "@") of [_Node, _Host] -> @@ -78,3 +111,28 @@ append_node_suffix(Name, Suffix) -> [Node] -> list_to_atom(lists:concat([Node, Suffix, os:getpid()])) end. + + +%% +%% Given a string or binary, parse it into a list of terms, ala file:consult/0 +%% +consult(Str) when is_list(Str) -> + consult([], Str, []); +consult(Bin) when is_binary(Bin)-> + consult([], binary_to_list(Bin), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + {ok, Term} = erl_parse:parse_term(Tokens), + consult([], Remaining, [Term | Acc]); + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end. diff --git a/priv/templates/simplenode.runner b/priv/templates/simplenode.runner index cfde552..18fa951 100755 --- a/priv/templates/simplenode.runner +++ b/priv/templates/simplenode.runner @@ -7,6 +7,7 @@ RUNNER_SCRIPT_DIR=$(cd ${0%/*} && 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= @@ -58,10 +59,10 @@ case "$1" in echo "Node is already running!" exit 1 fi - export HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" + HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" + export HEART_COMMAND mkdir -p $PIPE_DIR - # Note the trailing slash on $PIPE_DIR/ - $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 + $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 ;; stop) @@ -70,16 +71,16 @@ case "$1" in Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) # PID COMMAND PID=`ps ax -o pid= -o command=|\ - grep "$RUNNER_BASE_DIR/.*/[b]eam"|cut -d' ' -f1` + 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"|cut -d' ' -f1` + 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"|cut -d' ' -f2` + PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` ;; esac $NODETOOL stop @@ -116,13 +117,19 @@ case "$1" in $ERTS_PATH/to_erl $PIPE_DIR ;; - console) + console|console_clean) + # .boot file typically just $SCRIPT (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful: + case "$1" in + console) BOOTFILE=$SCRIPT ;; + console_clean) BOOTFILE=start_clean ;; + esac # Setup beam-required vars ROOTDIR=$RUNNER_BASE_DIR BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin EMU=beam PROGNAME=`echo $0 | sed 's/.*\\///'` - CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" + CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" export EMU export ROOTDIR export BINDIR @@ -1,3 +1,4 @@ @echo off +setlocal set rebarscript=%0 escript.exe %rebarscript:.bat=% %* diff --git a/rebar.config.sample b/rebar.config.sample index f963c30..184c16d 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 ft=erlang et %% This is a sample rebar.conf file that shows examples of some of rebar's %% options. @@ -31,7 +31,8 @@ {erl_opts, [{i, "myinclude"}, {src_dirs, ["src1", "src2"]}, {platform_define, "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'}, - {platform_define, "(linux|freebsd)", 'BACKLOG', 128}]}. + {platform_define, "(linux|freebsd)", 'BACKLOG', 128}, + {platform_define, "R13", 'old_inets'}]}. %% MIB Options? {mib_opts, []}. @@ -85,6 +86,9 @@ %% Additional compile options for eunit. erl_opts from above is also used {eunit_compile_opts, []}. +%% Same as erl_first_files, but used only when running 'eunit' +{eunit_first_files, []}. + %% Whether to enable coverage reporting. Default is `false' {cover_enabled, false}. diff --git a/src/getopt.erl b/src/getopt.erl index bb7fae2..35e19ec 100644 --- a/src/getopt.erl +++ b/src/getopt.erl @@ -13,6 +13,13 @@ -export([parse/2, usage/2, usage/3, usage/4]). +-export_type([arg_type/0, + arg_value/0, + arg_spec/0, + simple_option/0, + compound_option/0, + option/0, + option_spec/0]). -define(TAB_LENGTH, 8). %% Indentation of the help messages in number of tabs. @@ -75,10 +82,10 @@ parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) -> % Any argument present after the terminator is not considered an option. {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}}; %% Process long options. -parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$-, $- | OptArg] = OptStr | Tail]) -> +parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["--" ++ OptArg = OptStr | Tail]) -> parse_option_long(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); %% Process short options. -parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$- | [_Char | _] = OptArg] = OptStr | Tail]) -> +parse(OptSpecList, OptAcc, ArgAcc, ArgPos, ["-" ++ ([_Char | _] = OptArg) = OptStr | Tail]) -> parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); %% Process non-option arguments. parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) -> @@ -111,11 +118,11 @@ parse_option_long(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg); Long -> - case lists:keysearch(Long, ?OPT_LONG, OptSpecList) of - {value, {Name, _Short, Long, undefined, _Help}} -> + case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of + {Name, _Short, Long, undefined, _Help} -> parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args); - - {value, {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec} -> + + {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec -> % The option argument string is empty, but the option requires % an argument, so we look into the next string in the list. parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); @@ -132,8 +139,8 @@ parse_option_long(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> [string()], string(), string(), string()) -> {ok, {[option()], [string()]}}. parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) -> - case lists:keysearch(Long, ?OPT_LONG, OptSpecList) of - {value, {_Name, _Short, Long, ArgSpec, _Help} = OptSpec} -> + case lists:keyfind(Long, ?OPT_LONG, OptSpecList) of + {_Name, _Short, Long, ArgSpec, _Help} = OptSpec -> case ArgSpec of undefined -> throw({error, {invalid_option_arg, OptStr}}); @@ -151,7 +158,7 @@ parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Lon split_assigned_arg(OptStr) -> split_assigned_arg(OptStr, OptStr, []). -split_assigned_arg(_OptStr, [$= | Tail], Acc) -> +split_assigned_arg(_OptStr, "=" ++ Tail, Acc) -> {lists:reverse(Acc), Tail}; split_assigned_arg(OptStr, [Char | Tail], Acc) -> split_assigned_arg(OptStr, Tail, [Char | Acc]); @@ -170,11 +177,11 @@ split_assigned_arg(OptStr, [], _Acc) -> -spec parse_option_short([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> {ok, {[option()], [string()]}}. parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, [Short | Arg]) -> - case lists:keysearch(Short, ?OPT_SHORT, OptSpecList) of - {value, {Name, Short, _Long, undefined, _Help}} -> + case lists:keyfind(Short, ?OPT_SHORT, OptSpecList) of + {Name, Short, _Long, undefined, _Help} -> parse_option_short(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, Arg); - {value, {_Name, Short, _Long, ArgSpec, _Help} = OptSpec} -> + {_Name, Short, _Long, ArgSpec, _Help} = OptSpec -> case Arg of [] -> % The option argument string is empty, but the option requires @@ -217,7 +224,7 @@ parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [] = Args, {Name, _Sh _ -> throw({error, {missing_option_arg, Name}}) end. - + %% @doc Find the option for the discrete argument in position specified in the %% Pos argument. @@ -318,7 +325,7 @@ is_arg_true(Arg) -> (Arg =:= "on") orelse (Arg =:= "enabled") orelse (Arg =:= "1"). - + -spec is_arg_false(string()) -> boolean(). is_arg_false(Arg) -> (Arg =:= "false") orelse (Arg =:= "f") orelse @@ -362,7 +369,7 @@ is_float_arg([_Head | _Tail]) -> false; is_float_arg([]) -> true. - + %% @doc Show a message on stdout indicating the command line options and %% arguments that are supported by the program. @@ -441,8 +448,8 @@ 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 + Prefix = + case Long of undefined -> case Short of % Neither short nor long form (non-option argument). diff --git a/src/rebar.erl b/src/rebar.erl index 74812f5..b83a1f7 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -26,16 +26,316 @@ %% ------------------------------------------------------------------- -module(rebar). --export([main/1]). +-export([main/1, + help/0, + parse_args/1, + version/0]). + +-include("rebar.hrl"). + +-ifndef(BUILD_TIME). +-define(BUILD_TIME, "undefined"). +-endif. + +-ifndef(VCS_INFO). +-define(VCS_INFO, "undefined"). +-endif. + +%% ==================================================================== +%% Public API +%% ==================================================================== main(Args) -> - case catch(rebar_core:run(Args)) of + case catch(run(Args)) of ok -> ok; {error, failed} -> halt(1); Error -> - %% Nothing should percolate up from rebar_core; dump this error to console + %% Nothing should percolate up from rebar_core; + %% Dump this error to console io:format("Uncaught error in rebar_core: ~p\n", [Error]), halt(1) end. + +%% ==================================================================== +%% Internal functions +%% ==================================================================== + +run(RawArgs) -> + %% Pre-load the rebar app so that we get default configuration + ok = application:load(rebar), + %% Parse out command line arguments -- what's left is a list of commands to + %% run -- and start running commands + run_aux(parse_args(RawArgs)). + +run_aux(["help"]) -> + help(), + ok; +run_aux(["version"]) -> + %% Display vsn and build time info + version(), + ok; +run_aux(Commands) -> + %% Make sure crypto is running + ok = crypto:start(), + + %% Initialize logging system + rebar_log:init(), + + %% Convert command strings to atoms + CommandAtoms = [list_to_atom(C) || C <- Commands], + + %% Determine the location of the rebar executable; important for pulling + %% resources out of the escript + rebar_config:set_global(escript, filename:absname(escript:script_name())), + ?DEBUG("Rebar location: ~p\n", + [rebar_config:get_global(escript, undefined)]), + + %% Note the top-level directory for reference + rebar_config:set_global(base_dir, filename:absname(rebar_utils:get_cwd())), + + %% Keep track of how many operations we do, so we can detect bad commands + erlang:put(operations, 0), + + %% Process each command, resetting any state between each one + rebar_core:process_commands(CommandAtoms). + +%% +%% print help/usage string +%% +help() -> + OptSpecList = option_spec_list(), + getopt:usage(OptSpecList, "rebar", + "[var=value,...] <command,...>", + [{"var=value", "rebar global variables (e.g. force=1)"}, + {"command", "Command to run (e.g. compile)"}]). + +%% +%% Parse command line arguments using getopt and also filtering out any +%% key=value pairs. What's left is the list of commands to run +%% +parse_args(Args) -> + %% Parse getopt options + OptSpecList = option_spec_list(), + case getopt:parse(OptSpecList, Args) of + {ok, {Options, NonOptArgs}} -> + %% Check options and maybe halt execution + ok = show_info_maybe_halt(Options, NonOptArgs), + + %% Set global variables based on getopt options + set_global_flag(Options, verbose), + set_global_flag(Options, force), + DefJobs = rebar_config:get_jobs(), + case proplists:get_value(jobs, Options, DefJobs) of + DefJobs -> + ok; + Jobs -> + rebar_config:set_global(jobs, Jobs) + end, + + %% Set the rebar config to use + case proplists:get_value(config, Options) of + undefined -> ok; + Conf -> rebar_config:set_global(config, Conf) + end, + + %% Filter all the flags (i.e. strings of form key=value) from the + %% command line arguments. What's left will be the commands to run. + unabbreviate_command_names(filter_flags(NonOptArgs, [])); + + {error, {Reason, Data}} -> + ?ERROR("Error: ~s ~p~n~n", [Reason, Data]), + help(), + halt(1) + end. + +%% +%% show version information and halt +%% +version() -> + {ok, Vsn} = application:get_key(rebar, vsn), + ?CONSOLE("rebar version: ~s date: ~s vcs: ~s\n", + [Vsn, ?BUILD_TIME, ?VCS_INFO]). + + +%% +%% set global flag based on getopt option boolean value +%% +set_global_flag(Options, Flag) -> + Value = case proplists:get_bool(Flag, Options) of + true -> + "1"; + false -> + "0" + end, + rebar_config:set_global(Flag, Value). + +%% +%% show info and maybe halt execution +%% +show_info_maybe_halt(Opts, NonOptArgs) -> + false = show_info_maybe_halt(help, Opts, fun help/0), + false = show_info_maybe_halt(commands, Opts, fun commands/0), + false = show_info_maybe_halt(version, Opts, fun version/0), + case NonOptArgs of + [] -> + ?CONSOLE("No command to run specified!~n",[]), + help(), + halt(1); + _ -> + ok + end. + +show_info_maybe_halt(O, Opts, F) -> + case proplists:get_bool(O, Opts) of + true -> + F(), + halt(0); + false -> + false + end. + +%% +%% print known commands +%% +commands() -> + S = <<" +dialyze Analyze with Dialyzer +build-plt Build Dialyzer PLT +check-plt Check Dialyzer PLT + +clean Clean +compile Compile sources + +create template= [var=foo,...] Create skel based on template and vars +create-app [appid=myapp] Create simple app skel +create-node [nodeid=mynode] Create simple node skel +list-templates List available templates + +doc Generate Erlang program documentation + +check-deps Display to be fetched dependencies +get-deps Fetch dependencies +update-deps Update fetched dependencies +delete-deps Delete fetched dependencies + +generate [dump_spec=0/1] Build release with reltool + +generate-upgrade previous_release=path Build an upgrade package + +generate-appups previous_release=path Generate appup files + +eunit [suite=foo] Run eunit [test/foo_tests.erl] tests +ct [suite=] [case=] Run common_test suites in ./test + +xref Run cross reference analysis + +help Show the program options +version Show version information +">>, + io:put_chars(S), + %% workaround to delay exit until all output is written + timer:sleep(300). + +%% +%% options accepted via getopt +%% +option_spec_list() -> + Jobs = rebar_config:get_jobs(), + JobsHelp = io_lib:format( + "Number of concurrent workers a command may use. Default: ~B", + [Jobs]), + [ + %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} + {help, $h, "help", undefined, "Show the program options"}, + {commands, $c, "commands", undefined, "Show available commands"}, + {verbose, $v, "verbose", undefined, "Be verbose about what gets done"}, + {version, $V, "version", undefined, "Show version information"}, + {force, $f, "force", undefined, "Force"}, + {jobs, $j, "jobs", integer, JobsHelp}, + {config, $C, "config", string, "Rebar config file to use"} + ]. + +%% +%% Seperate all commands (single-words) from flags (key=value) and store +%% values into the rebar_config global storage. +%% +filter_flags([], Commands) -> + lists:reverse(Commands); +filter_flags([Item | Rest], Commands) -> + case string:tokens(Item, "=") of + [Command] -> + filter_flags(Rest, [Command | Commands]); + [KeyStr, Value] -> + Key = list_to_atom(KeyStr), + rebar_config:set_global(Key, Value), + filter_flags(Rest, Commands); + Other -> + ?CONSOLE("Ignoring command line argument: ~p\n", [Other]), + filter_flags(Rest, Commands) + end. + +command_names() -> + ["build-plt", "check-deps", "check-plt", "clean", "compile", "create", + "create-app", "create-node", "ct", "delete-deps", "dialyze", "doc", + "eunit", "generate", "generate-appups", "generate-upgrade", "get-deps", + "help", "list-templates", "update-deps", "version", "xref"]. + +unabbreviate_command_names([]) -> + []; +unabbreviate_command_names([Command | Commands]) -> + case get_command_name_candidates(Command) of + [] -> + %% let the rest of the code detect that the command doesn't exist + %% (this would perhaps be a good place to fail) + [Command | unabbreviate_command_names(Commands)]; + [FullCommand] -> + [FullCommand | unabbreviate_command_names(Commands)]; + Candidates -> + ?ABORT("Found more than one match for abbreviated command name " + " '~s',~nplease be more specific. Possible candidates:~n" + " ~s~n", + [Command, string:join(Candidates, ", ")]) + end. + +get_command_name_candidates(Command) -> + %% Get the command names which match the given (abbreviated) command name. + %% * "c" matches commands like compile, clean and create-app + %% * "create" matches command create only, since it's unique + %% * "create-" matches commands starting with create- + %% * "c-a" matches create-app + %% * "create-a" matches create-app + %% * "c-app" matches create-app + Candidates = [Candidate || Candidate <- command_names(), + is_command_name_candidate(Command, Candidate)], + %% Is there a complete match? If so return only that, return a + %% list of candidates otherwise + case lists:member(Command, Candidates) of + true -> [Command]; + false -> Candidates + end. + +is_command_name_candidate(Command, Candidate) -> + lists:prefix(Command, Candidate) + orelse is_command_name_sub_word_candidate(Command, Candidate). + +is_command_name_sub_word_candidate(Command, Candidate) -> + %% Allow for parts of commands to be abbreviated, i.e. create-app + %% can be shortened to "create-a", "c-a" or "c-app" (but not + %% "create-" since that would be ambiguous). + CommandSubWords = re:split(Command, "-", [{return, list}]), + CandidateSubWords = re:split(Candidate, "-", [{return, list}]), + is_command_name_sub_word_candidate_aux(CommandSubWords, CandidateSubWords). + +is_command_name_sub_word_candidate_aux([CmdSW | CmdSWs], [CandSW | CandSWs]) -> + case lists:prefix(CmdSW, CandSW) of + true -> + is_command_name_sub_word_candidate_aux(CmdSWs, CandSWs); + false -> + false + end; +is_command_name_sub_word_candidate_aux([], []) -> + true; +is_command_name_sub_word_candidate_aux(_CmdSWs, _CandSWs) -> + false. diff --git a/src/rebar_abnfc_compiler.erl b/src/rebar_abnfc_compiler.erl new file mode 100644 index 0000000..bfb9a35 --- /dev/null +++ b/src/rebar_abnfc_compiler.erl @@ -0,0 +1,109 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2010 Anthony Ramine (nox@dev-extend.eu), +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- + +%% The rebar_abnfc_compiler module is a plugin for rebar that compiles +%% ABNF grammars into parsers. By default, it compiles all src/*.abnf +%% to src/*.erl. +%% +%% Configuration options should be placed in rebar.config under +%% 'abnfc_opts'. Available options include: +%% +%% doc_root: where to find the ABNF grammars to compile +%% "src" by default +%% +%% out_dir: where to put the generated files. +%% "src" by default +%% +%% source_ext: the file extension the ABNF grammars have. +%% ".abnf" by default +%% +%% module_ext: characters to append to the parser's module name +%% "" by default +-module(rebar_abnfc_compiler). + +-export([compile/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +compile(Config, _AppFile) -> + DtlOpts = abnfc_opts(Config), + rebar_base_compiler:run(Config, [], + option(doc_root, DtlOpts), + option(source_ext, DtlOpts), + option(out_dir, DtlOpts), + option(module_ext, DtlOpts) ++ ".erl", + fun compile_abnfc/3). + + +%% =================================================================== +%% Internal functions +%% =================================================================== + +abnfc_opts(Config) -> + rebar_config:get(Config, abnfc_opts, []). + +option(Opt, DtlOpts) -> + proplists:get_value(Opt, DtlOpts, default(Opt)). + +default(doc_root) -> "src"; +default(out_dir) -> "src"; +default(source_ext) -> ".abnf"; +default(module_ext) -> "". + +abnfc_is_present() -> + code:which(abnfc) =/= non_existing. + +compile_abnfc(Source, _Target, Config) -> + case abnfc_is_present() of + false -> + ?CONSOLE( + <<"~n===============================================~n" + " You need to install abnfc to compile ABNF grammars~n" + " Download the latest tarball release from github~n" + " https://github.com/nygge/abnfc~n" + " and install it into your erlang library dir~n" + "===============================================~n~n">>, []), + ?FAIL; + true -> + AbnfcOpts = abnfc_opts(Config), + SourceExt = option(source_ext, AbnfcOpts), + Opts = [noobj, + {o, option(out_dir, AbnfcOpts)}, + {mod, filename:basename(Source, SourceExt) ++ + option(module_ext, AbnfcOpts)}], + case abnfc:file(Source, Opts) of + ok -> ok; + Error -> + ?CONSOLE("Compiling grammar ~s failed:~n ~p~n", + [Source, Error]), + ?FAIL + end + end. diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl index 9574775..27b9176 100644 --- a/src/rebar_app_utils.erl +++ b/src/rebar_app_utils.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -80,7 +80,7 @@ app_name(AppFile) -> app_applications(AppFile) -> case load_app_file(AppFile) of {ok, _, AppInfo} -> - proplists:get_value(applications, AppInfo); + get_value(applications, AppInfo, AppFile); {error, Reason} -> ?ABORT("Failed to extract applications from ~s: ~p\n", [AppFile, Reason]) @@ -89,7 +89,7 @@ app_applications(AppFile) -> app_vsn(AppFile) -> case load_app_file(AppFile) of {ok, _, AppInfo} -> - proplists:get_value(vsn, AppInfo); + vcs_vsn(get_value(vsn, AppInfo, AppFile)); {error, Reason} -> ?ABORT("Failed to extract vsn from ~s: ~p\n", [AppFile, Reason]) @@ -116,3 +116,26 @@ load_app_file(Filename) -> {AppName, AppData} -> {ok, AppName, AppData} end. + +get_value(Key, AppInfo, AppFile) -> + case proplists:get_value(Key, AppInfo) of + undefined -> + ?ABORT("Failed to get app value '~p' from '~s'~n", [Key, AppFile]); + Value -> + Value + end. + +vcs_vsn(Vcs) -> + case vcs_vsn_cmd(Vcs) of + {unknown, VsnString} -> + VsnString; + Cmd -> + {ok, VsnString} = rebar_utils:sh(Cmd, [{use_stdout, false}]), + string:strip(VsnString, right, $\n) + end. + +vcs_vsn_cmd(git) -> "git describe --always --tags"; +vcs_vsn_cmd(hg) -> "hg identify -i"; +vcs_vsn_cmd(bzr) -> "bzr revno"; +vcs_vsn_cmd(svn) -> "svnversion"; +vcs_vsn_cmd(Version) -> {unknown, Version}. diff --git a/src/rebar_appups.erl b/src/rebar_appups.erl new file mode 100644 index 0000000..ab5af29 --- /dev/null +++ b/src/rebar_appups.erl @@ -0,0 +1,179 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2011 Joe Williams (joe@joetify.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------ + +-module(rebar_appups). + +-include("rebar.hrl"). + +-export(['generate-appups'/2]). + +-define(APPUPFILEFORMAT, "%% appup generated for ~p by rebar (~p)~n" + "{~p, [{~p, ~p}], [{~p, []}]}.~n"). + +%% ==================================================================== +%% Public API +%% ==================================================================== + +'generate-appups'(_Config, ReltoolFile) -> + %% Get the old release path + OldVerPath = rebar_rel_utils:get_previous_release_path(), + + %% Get the new and old release name and versions + {Name, _Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolFile), + NewVerPath = filename:join([".", Name]), + {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NewVerPath), + {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), + + %% Run some simple checks + true = rebar_utils:prop_check(NewVer =/= OldVer, + "New and old .rel versions match~n", []), + true = rebar_utils:prop_check( + NewName == OldName, + "Reltool and .rel release names do not match~n", []), + + %% Get lists of the old and new app files + OldAppFiles = rebar_utils:find_files( + filename:join([OldVerPath, "lib"]), "^.*.app$"), + NewAppFiles = rebar_utils:find_files( + filename:join([NewName, "lib"]), "^.*.app$"), + + %% Find all the apps that have been upgraded + UpgradedApps = get_upgraded_apps(OldAppFiles, NewAppFiles), + + %% Get a list of any appup files that exist in the new release + NewAppUpFiles = rebar_utils:find_files( + filename:join([NewName, "lib"]), "^.*.appup$"), + + %% Convert the list of appup files into app names + AppUpApps = lists:map(fun(File) -> + file_to_name(File) + end, NewAppUpFiles), + + %% Create a list of apps that don't already have appups + Apps = genappup_which_apps(UpgradedApps, AppUpApps), + + %% Generate appup files + generate_appup_files(Name, OldVerPath, Apps), + + ok. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +get_upgraded_apps(OldAppFiles, NewAppFiles) -> + OldAppsVer = [{rebar_app_utils:app_name(AppFile), + rebar_app_utils:app_vsn(AppFile)} || AppFile <- OldAppFiles], + NewAppsVer = [{rebar_app_utils:app_name(AppFile), + rebar_app_utils:app_vsn(AppFile)} || AppFile <- NewAppFiles], + UpgradedApps = lists:subtract(NewAppsVer, OldAppsVer), + lists:map( + fun({App, NewVer}) -> + {App, OldVer} = proplists:lookup(App, OldAppsVer), + {App, {OldVer, NewVer}} + end, + UpgradedApps). + +file_to_name(File) -> + filename:rootname(filename:basename(File)). + +genappup_which_apps(UpgradedApps, [First|Rest]) -> + List = proplists:delete(list_to_atom(First), UpgradedApps), + genappup_which_apps(List, Rest); +genappup_which_apps(Apps, []) -> + Apps. + +generate_appup_files(Name, OldVerPath, [{App, {OldVer, NewVer}}|Rest]) -> + OldEbinDir = filename:join([".", OldVerPath, "lib", + atom_to_list(App) ++ "-" ++ OldVer, "ebin"]), + NewEbinDir = filename:join([".", Name, "lib", + atom_to_list(App) ++ "-" ++ NewVer, "ebin"]), + + {AddedFiles, DeletedFiles, ChangedFiles} = beam_lib:cmp_dirs(NewEbinDir, + OldEbinDir), + + Added = [generate_instruction(added, File) || File <- AddedFiles], + Deleted = [generate_instruction(deleted, File) || File <- DeletedFiles], + Changed = [generate_instruction(changed, File) || File <- ChangedFiles], + + Inst = lists:append([Added, Deleted, Changed]), + + AppUpFile = filename:join([NewEbinDir, atom_to_list(App) ++ ".appup"]), + + ok = file:write_file(AppUpFile, + io_lib:fwrite(?APPUPFILEFORMAT, + [App, rebar_utils:now_str(), NewVer, + OldVer, Inst, OldVer])), + + ?CONSOLE("Generated appup for ~p~n", [App]), + generate_appup_files(Name, OldVerPath, Rest); +generate_appup_files(_, _, []) -> + ?CONSOLE("Appup generation complete~n", []). + +generate_instruction(added, File) -> + Name = list_to_atom(file_to_name(File)), + {add_module, Name}; +generate_instruction(deleted, File) -> + Name = list_to_atom(file_to_name(File)), + {delete_module, Name}; +generate_instruction(changed, {File, _}) -> + {ok, {Name, List}} = beam_lib:chunks(File, [attributes, exports]), + Behavior = get_behavior(List), + CodeChange = is_code_change(List), + generate_instruction_advanced(Name, Behavior, CodeChange). + +generate_instruction_advanced(Name, undefined, undefined) -> + %% Not a behavior or code change, assume purely functional + {load_module, Name}; +generate_instruction_advanced(Name, [supervisor], _) -> + %% Supervisor + {update, Name, supervisor}; +generate_instruction_advanced(Name, _, code_change) -> + %% Includes code_change export + {update, Name, {advanced, []}}; +generate_instruction_advanced(Name, _, _) -> + %% Anything else + {update, Name}. + +get_behavior(List) -> + Attributes = proplists:get_value(attributes, List), + Behavior = case proplists:get_value(behavior, Attributes) of + undefined -> + proplists:get_value(behaviour, Attributes); + Else -> + Else + end, + Behavior. + +is_code_change(List) -> + Exports = proplists:get_value(exports, List), + case proplists:is_defined(code_change, Exports) of + true -> + code_change; + false -> + undefined + end. diff --git a/src/rebar_asn1_compiler.erl b/src/rebar_asn1_compiler.erl index 39614f0..750ff41 100644 --- a/src/rebar_asn1_compiler.erl +++ b/src/rebar_asn1_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl index aea55df..10a495d 100644 --- a/src/rebar_base_compiler.erl +++ b/src/rebar_base_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -52,7 +52,8 @@ run(Config, FirstFiles, RestFiles, CompileFn) -> compile_queue(Pids, RestFiles) end. -run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, Compile3Fn) -> +run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, + Compile3Fn) -> run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, Compile3Fn, [check_last_mod]). @@ -73,7 +74,8 @@ run(Config, FirstFiles, SourceDir, SourceExt, TargetDir, TargetExt, run(Config, FirstFiles, RestFiles, fun(S, C) -> - Target = target_file(S, SourceDir, SourceExt, TargetDir, TargetExt), + Target = target_file(S, SourceDir, SourceExt, + TargetDir, TargetExt), simple_compile_wrapper(S, Target, Compile3Fn, C, CheckLastMod) end). diff --git a/src/rebar_cleaner.erl b/src/rebar_cleaner.erl index 2ff828e..9ddeb8a 100644 --- a/src/rebar_cleaner.erl +++ b/src/rebar_cleaner.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% diff --git a/src/rebar_config.erl b/src/rebar_config.erl index cba0e64..a030e2c 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -58,20 +58,23 @@ new(ParentConfig) -> %% Load terms from rebar.config, if it exists Dir = rebar_utils:get_cwd(), ConfigFile = filename:join([Dir, ConfName]), - case file:consult(ConfigFile) of - {ok, Terms} -> - %% Found a config file with some terms. We need to be able to - %% distinguish between local definitions (i.e. from the file - %% in the cwd) and inherited definitions. To accomplish this, - %% we use a marker in the proplist (since order matters) between - %% the new and old defs. - Opts = Terms ++ [local] ++ [Opt || Opt <- ParentConfig#config.opts, Opt /= local]; - {error, enoent} -> - Opts = [local] ++ [Opt || Opt <- ParentConfig#config.opts, Opt /= local]; - Other -> - Opts = undefined, % Keep erlc happy - ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other]) - end, + Opts = case file:consult(ConfigFile) of + {ok, Terms} -> + %% Found a config file with some terms. We need to + %% be able to distinguish between local definitions + %% (i.e. from the file in the cwd) and inherited + %% definitions. To accomplish this, we use a marker + %% in the proplist (since order matters) between + %% the new and old defs. + Terms ++ [local] ++ + [Opt || Opt <- ParentConfig#config.opts, Opt /= local]; + {error, enoent} -> + [local] ++ + [Opt || Opt <- ParentConfig#config.opts, Opt /= local]; + Other -> + ?ABORT("Failed to load ~s: ~p\n", [ConfigFile, Other]) + end, + #config { dir = Dir, opts = Opts }. get(Config, Key, Default) -> @@ -91,7 +94,7 @@ set(Config, Key, Value) -> Config#config { opts = [{Key, Value} | Opts] }. set_global(jobs=Key, Value) when is_list(Value) -> - set_global(Key,list_to_integer(Value)); + set_global(Key, list_to_integer(Value)); set_global(jobs=Key, Value) when is_integer(Value) -> application:set_env(rebar_global, Key, erlang:max(1,Value)); set_global(Key, Value) -> diff --git a/src/rebar_core.erl b/src/rebar_core.erl index 3c64f8d..db3e0b4 100644 --- a/src/rebar_core.erl +++ b/src/rebar_core.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -26,7 +26,7 @@ %% ------------------------------------------------------------------- -module(rebar_core). --export([run/1, +-export([process_commands/1, skip_dir/1, is_skip_dir/1, skip_dirs/0]). @@ -34,57 +34,10 @@ -include("rebar.hrl"). --ifndef(BUILD_TIME). --define(BUILD_TIME, "undefined"). --endif. - --ifndef(VCS_INFO). --define(VCS_INFO, "undefined"). --endif. - %% =================================================================== %% Public API %% =================================================================== -run(["help"]) -> - help(), - ok; -run(["version"]) -> - %% Load application spec and display vsn and build time info - ok = application:load(rebar), - version(), - ok; -run(RawArgs) -> - %% Pre-load the rebar app so that we get default configuration - ok = application:load(rebar), - - %% Parse out command line arguments -- what's left is a list of commands to - %% run - Commands = parse_args(RawArgs), - - %% Make sure crypto is running - ok = crypto:start(), - - %% Initialize logging system - rebar_log:init(), - - %% Convert command strings to atoms - CommandAtoms = [list_to_atom(C) || C <- Commands], - - %% Determine the location of the rebar executable; important for pulling - %% resources out of the escript - rebar_config:set_global(escript, filename:absname(escript:script_name())), - ?DEBUG("Rebar location: ~p\n", [rebar_config:get_global(escript, undefined)]), - - %% Note the top-level directory for reference - rebar_config:set_global(base_dir, filename:absname(rebar_utils:get_cwd())), - - %% Keep track of how many operations we do, so we can detect bad commands - erlang:put(operations, 0), - - %% Process each command, resetting any state between each one - process_commands(CommandAtoms). - skip_dir(Dir) -> SkipDir = {skip_dir, Dir}, case erlang:get(SkipDir) of @@ -97,10 +50,10 @@ skip_dir(Dir) -> is_skip_dir(Dir) -> case erlang:get({skip_dir, Dir}) of - undefined -> - false; - true -> - true + undefined -> + false; + true -> + true end. skip_dirs() -> @@ -110,180 +63,6 @@ skip_dirs() -> %% Internal functions %% =================================================================== -%% -%% Parse command line arguments using getopt and also filtering out any -%% key=value pairs. What's left is the list of commands to run -%% -parse_args(Args) -> - %% Parse getopt options - OptSpecList = option_spec_list(), - case getopt:parse(OptSpecList, Args) of - {ok, {Options, NonOptArgs}} -> - %% Check options and maybe halt execution - {ok, continue} = show_info_maybe_halt(Options, NonOptArgs), - - %% Set global variables based on getopt options - set_global_flag(Options, verbose), - set_global_flag(Options, force), - DefJobs = rebar_config:get_jobs(), - case proplists:get_value(jobs, Options, DefJobs) of - DefJobs -> - ok; - Jobs -> - rebar_config:set_global(jobs, Jobs) - end, - - %% Set the rebar config to use - case proplists:get_value(config, Options) of - undefined -> ok; - Conf -> rebar_config:set_global(config, Conf) - end, - - %% Filter all the flags (i.e. strings of form key=value) from the - %% command line arguments. What's left will be the commands to run. - filter_flags(NonOptArgs, []); - - {error, {Reason, Data}} -> - ?ERROR("Error: ~s ~p~n~n", [Reason, Data]), - help(), - halt(1) - end. - -%% -%% set global flag based on getopt option boolean value -%% -set_global_flag(Options, Flag) -> - Value = case proplists:get_bool(Flag, Options) of - true -> - "1"; - false -> - "0" - end, - rebar_config:set_global(Flag, Value). - -%% -%% show info and maybe halt execution -%% -show_info_maybe_halt(Opts, NonOptArgs) -> - case proplists:get_bool(help, Opts) of - true -> - help(), - halt(0); - false -> - case proplists:get_bool(commands, Opts) of - true -> - commands(), - halt(0); - false -> - case proplists:get_bool(version, Opts) of - true -> - version(), - halt(0); - false -> - case NonOptArgs of - [] -> - ?CONSOLE("No command to run specified!~n",[]), - help(), - halt(1); - _ -> - {ok, continue} - end - end - end - end. - -%% -%% print help/usage string -%% -help() -> - OptSpecList = option_spec_list(), - getopt:usage(OptSpecList, "rebar", - "[var=value,...] <command,...>", - [{"var=value", "rebar global variables (e.g. force=1)"}, - {"command", "Command to run (e.g. compile)"}]). - -%% -%% print known commands -%% -commands() -> - S = <<" -dialyze Analyze with Dialyzer -build-plt Build Dialyzer PLT -check-plt Check Dialyzer PLT - -clean Clean -compile Compile sources - -create template= [var=foo,...] Create skel based on template and vars -create-app [appid=myapp] Create simple app skel -create-node [nodeid=mynode] Create simple node skel -list-templates List available templates - -doc Generate Erlang program documentation - -check-deps Display to be fetched dependencies -get-deps Fetch dependencies -delete-deps Delete fetched dependencies - -generate [dump_spec=0/1] Build release with reltool - -eunit [suite=foo] Run eunit [test/foo_tests.erl] tests -ct [suite=] [case=] Run common_test suites in ./test - -xref Run cross reference analysis - -help Show the program options -version Show version information -">>, - io:put_chars(S), - %% workaround to delay exit until all output is written - timer:sleep(300). - -%% -%% show version information and halt -%% -version() -> - {ok, Vsn} = application:get_key(rebar, vsn), - ?CONSOLE("rebar version: ~s date: ~s vcs: ~s\n", [Vsn, ?BUILD_TIME, ?VCS_INFO]). - -%% -%% options accepted via getopt -%% -option_spec_list() -> - Jobs = rebar_config:get_jobs(), - JobsHelp = io_lib:format( - "Number of concurrent workers a command may use. Default: ~B", - [Jobs]), - [ - %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} - {help, $h, "help", undefined, "Show the program options"}, - {commands, $c, "commands", undefined, "Show available commands"}, - {verbose, $v, "verbose", undefined, "Be verbose about what gets done"}, - {version, $V, "version", undefined, "Show version information"}, - {force, $f, "force", undefined, "Force"}, - {jobs, $j, "jobs", integer, JobsHelp}, - {config, $C, "config", string, "Rebar config file to use"} - ]. - -%% -%% Seperate all commands (single-words) from flags (key=value) and store -%% values into the rebar_config global storage. -%% -filter_flags([], Commands) -> - lists:reverse(Commands); -filter_flags([Item | Rest], Commands) -> - case string:tokens(Item, "=") of - [Command] -> - filter_flags(Rest, [Command | Commands]); - [KeyStr, Value] -> - Key = list_to_atom(KeyStr), - rebar_config:set_global(Key, Value), - filter_flags(Rest, Commands); - Other -> - ?CONSOLE("Ignoring command line argument: ~p\n", [Other]), - filter_flags(Rest, Commands) - end. - process_commands([]) -> case erlang:get(operations) of 0 -> @@ -297,7 +76,8 @@ process_commands([Command | Rest]) -> lists:foreach(fun (D) -> erlang:erase({skip_dir, D}) end, skip_dirs()), Operations = erlang:get(operations), - _ = process_dir(rebar_utils:get_cwd(), rebar_config:new(), Command, sets:new()), + _ = process_dir(rebar_utils:get_cwd(), rebar_config:new(), + Command, sets:new()), case erlang:get(operations) of Operations -> %% This command didn't do anything @@ -329,7 +109,8 @@ process_dir(Dir, ParentConfig, Command, DirSet) -> %% CWD to see if it's a fit -- if it is, use that set of modules %% to process this dir. {ok, AvailModuleSets} = application:get_env(rebar, modules), - {DirModules, ModuleSetFile} = choose_module_set(AvailModuleSets, Dir), + {DirModules, ModuleSetFile} = choose_module_set(AvailModuleSets, + Dir), %% Get the list of modules for "any dir". This is a catch-all list %% of modules that are processed in addition to modules associated @@ -343,7 +124,8 @@ process_dir(Dir, ParentConfig, Command, DirSet) -> %% directories that should be processed _before_ the current one. Predirs = acc_modules(Modules, preprocess, Config, ModuleSetFile), ?DEBUG("Predirs: ~p\n", [Predirs]), - DirSet2 = process_each(Predirs, Command, Config, ModuleSetFile, DirSet), + DirSet2 = process_each(Predirs, Command, Config, + ModuleSetFile, DirSet), %% Make sure the CWD is reset properly; processing the dirs may have %% caused it to change @@ -352,27 +134,30 @@ process_dir(Dir, ParentConfig, Command, DirSet) -> %% Check that this directory is not on the skip list case is_skip_dir(Dir) of true -> - %% Do not execute the command on the directory, as some module - %% as requested a skip on it. + %% Do not execute the command on the directory, as some + %% module as requested a skip on it. ?INFO("Skipping ~s in ~s\n", [Command, Dir]); false -> - %% Get the list of plug-in modules from rebar.config. These modules are - %% processed LAST and do not participate in preprocess. + %% Get the list of plug-in modules from rebar.config. These + %% modules are processed LAST and do not participate + %% in preprocess. {ok, PluginModules} = plugin_modules(Config), %% Execute the current command on this directory - execute(Command, Modules ++ PluginModules, Config, ModuleSetFile) + execute(Command, Modules ++ PluginModules, + Config, ModuleSetFile) end, %% Mark the current directory as processed DirSet3 = sets:add_element(Dir, DirSet2), - %% Invoke 'postprocess' on the modules -- this yields a list of other + %% Invoke 'postprocess' on the modules. This yields a list of other %% directories that should be processed _after_ the current one. Postdirs = acc_modules(Modules, postprocess, Config, ModuleSetFile), ?DEBUG("Postdirs: ~p\n", [Postdirs]), - DirSet4 = process_each(Postdirs, Command, Config, ModuleSetFile, DirSet3), + DirSet4 = process_each(Postdirs, Command, Config, + ModuleSetFile, DirSet3), %% Make sure the CWD is reset properly; processing the dirs may have %% caused it to change @@ -441,21 +226,24 @@ execute(Command, Modules, Config, ModuleFile) -> Dir = rebar_utils:get_cwd(), ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]), - %% Increment the count of operations, since some module responds to this command + %% Increment the count of operations, since some module + %% responds to this command erlang:put(operations, erlang:get(operations) + 1), %% Run the available modules - case catch(run_modules(TargetModules, Command, Config, ModuleFile)) of + case catch(run_modules(TargetModules, Command, + Config, ModuleFile)) of ok -> ok; {error, failed} -> ?FAIL; {Module, {error, _} = Other} -> ?ABORT("~p failed while processing ~s in module ~s: ~s\n", - [Command, Dir, Module, io_lib:print(Other, 1,80,-1)]); + [Command, Dir, Module, + io_lib:print(Other, 1, 80, -1)]); Other -> ?ABORT("~p failed while processing ~s: ~s\n", - [Command, Dir, io_lib:print(Other, 1,80,-1)]) + [Command, Dir, io_lib:print(Other, 1, 80, -1)]) end end. @@ -474,8 +262,8 @@ update_code_path(Config) -> restore_code_path(no_change) -> ok; restore_code_path({old, Path}) -> - %% Verify that all of the paths still exist -- some dynamically add paths - %% can get blown away during clean. + %% Verify that all of the paths still exist -- some dynamically + %% added paths can get blown away during clean. true = code:set_path([F || F <- Path, filelib:is_file(F)]), ok. diff --git a/src/rebar_ct.erl b/src/rebar_ct.erl index 316998f..8f7c71a 100644 --- a/src/rebar_ct.erl +++ b/src/rebar_ct.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -71,51 +71,53 @@ run_test(TestDir, Config, _File) -> Output = " 2>&1 | tee -a " ++ RawLog end, - rebar_utils:sh(Cmd ++ Output, [{"TESTDIR", TestDir}]), + rebar_utils:sh(Cmd ++ Output, [{env,[{"TESTDIR", TestDir}]}]), check_log(RawLog). clear_log(RawLog) -> case filelib:ensure_dir("logs/index.html") of - ok -> - NowStr = rebar_utils:now_str(), - LogHeader = "--- Test run on " ++ NowStr ++ " ---\n", - ok = file:write_file(RawLog, LogHeader); - {error, Reason} -> - ?ERROR("Could not create log dir - ~p\n", [Reason]), - ?FAIL + ok -> + NowStr = rebar_utils:now_str(), + LogHeader = "--- Test run on " ++ NowStr ++ " ---\n", + ok = file:write_file(RawLog, LogHeader); + {error, Reason} -> + ?ERROR("Could not create log dir - ~p\n", [Reason]), + ?FAIL end. %% calling ct with erl does not return non-zero on failure - have to check %% log results check_log(RawLog) -> - Msg = os:cmd("grep -e 'TEST COMPLETE' -e '{error,make_failed}' " ++ RawLog), + {ok, Msg} = + rebar_utils:sh("grep -e 'TEST COMPLETE' -e '{error,make_failed}' " + ++ RawLog, [{use_stdout, false}]), MakeFailed = string:str(Msg, "{error,make_failed}") =/= 0, RunFailed = string:str(Msg, ", 0 failed") =:= 0, if MakeFailed -> - show_log(RawLog), - ?ERROR("Building tests failed\n",[]), - ?FAIL; + show_log(RawLog), + ?ERROR("Building tests failed\n",[]), + ?FAIL; RunFailed -> - show_log(RawLog), - ?ERROR("One or more tests failed\n",[]), - ?FAIL; + show_log(RawLog), + ?ERROR("One or more tests failed\n",[]), + ?FAIL; - true -> - ?CONSOLE("DONE. ~s\n", [Msg]) + true -> + ?CONSOLE("DONE. ~s\n", [Msg]) end. %% Show the log if it hasn't already been shown because verbose was on show_log(RawLog) -> ?CONSOLE("Showing log\n", []), case rebar_config:get_global(verbose, "0") of - "0" -> - {ok, Contents} = file:read_file(RawLog), - ?CONSOLE("~s", [Contents]); - _ -> - ok + "0" -> + {ok, Contents} = file:read_file(RawLog), + ?CONSOLE("~s", [Contents]); + _ -> + ok end. make_cmd(TestDir, Config) -> @@ -135,7 +137,7 @@ make_cmd(TestDir, Config) -> %% that are part of the root Erlang install are filtered out to %% avoid duplication R = code:root_dir(), - NonLibCodeDirs = [P || P <- code:get_path(), lists:prefix(R, P) == false], + NonLibCodeDirs = [P || P <- code:get_path(), not lists:prefix(R, P)], CodeDirs = [io_lib:format("\"~s\"", [Dir]) || Dir <- [EbinDir|NonLibCodeDirs]], CodePathString = string:join(CodeDirs, " "), @@ -163,21 +165,22 @@ get_cover_config(Config, Cwd) -> false -> ""; true -> - case filelib:fold_files(Cwd, ".*cover\.spec\$", true, fun collect_ct_specs/2, []) of - [] -> - ?DEBUG("No cover spec found: ~s~n", [Cwd]), - ""; - [Spec] -> - ?DEBUG("Found cover file ~w~n", [Spec]), - " -cover " ++ Spec; - Specs -> - ?ABORT("Multiple cover specs found: ~p~n", [Specs]) - end - end. + case filelib:fold_files(Cwd, ".*cover\.spec\$", + true, fun collect_ct_specs/2, []) of + [] -> + ?DEBUG("No cover spec found: ~s~n", [Cwd]), + ""; + [Spec] -> + ?DEBUG("Found cover file ~w~n", [Spec]), + " -cover " ++ Spec; + Specs -> + ?ABORT("Multiple cover specs found: ~p~n", [Specs]) + end + end. collect_ct_specs(F, Acc) -> - %% Ignore any specs under the deps/ directory. Do this pulling the dirname off the - %% the F and then splitting it into a list. + %% Ignore any specs under the deps/ directory. Do this pulling + %% the dirname off the the F and then splitting it into a list. Parts = filename:split(filename:dirname(F)), case lists:member("deps", Parts) of true -> diff --git a/src/rebar_deps.erl b/src/rebar_deps.erl index 0852b01..69f5fc1 100644 --- a/src/rebar_deps.erl +++ b/src/rebar_deps.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -33,6 +33,7 @@ compile/2, 'check-deps'/2, 'get-deps'/2, + 'update-deps'/2, 'delete-deps'/2]). @@ -46,15 +47,15 @@ %% =================================================================== preprocess(Config, _) -> - %% Side effect to set deps_dir globally for all dependencies from top level down. - %% Means the root deps_dir is honoured or the default used globally - %% since it will be set on the first time through here + %% Side effect to set deps_dir globally for all dependencies from + %% top level down. Means the root deps_dir is honoured or the default + %% used globally since it will be set on the first time through here set_global_deps_dir(Config, rebar_config:get_global(deps_dir, [])), %% Get the list of deps for the current working directory and identify those %% deps that are available/present. Deps = rebar_config:get_local(Config, deps, []), - {AvailableDeps, MissingDeps} = find_deps(Deps), + {AvailableDeps, MissingDeps} = find_deps(find, Deps), ?DEBUG("Available deps: ~p\n", [AvailableDeps]), ?DEBUG("Missing deps : ~p\n", [MissingDeps]), @@ -68,8 +69,8 @@ preprocess(Config, _) -> case rebar_config:get_global(skip_deps, false) of "true" -> lists:foreach(fun (#dep{dir = Dir}) -> - rebar_core:skip_dir(Dir) - end, AvailableDeps); + rebar_core:skip_dir(Dir) + end, AvailableDeps); _ -> ok end, @@ -93,43 +94,49 @@ compile(Config, AppFile) -> 'check-deps'(Config, _) -> %% Get the list of immediate (i.e. non-transitive) deps that are missing Deps = rebar_config:get_local(Config, deps, []), - case find_deps(Deps) of + case find_deps(find, Deps) of {_, []} -> %% No missing deps ok; {_, MissingDeps} -> lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) -> - ?CONSOLE("Dependency not available: ~p-~s (~p)\n", - [App, Vsn, Src]) - end, MissingDeps), + ?CONSOLE("Dependency not available: " + "~p-~s (~p)\n", [App, Vsn, Src]) + end, MissingDeps), ?FAIL end. 'get-deps'(Config, _) -> %% Determine what deps are available and missing Deps = rebar_config:get_local(Config, deps, []), - {AvailableDeps, MissingDeps} = find_deps(Deps), + {_AvailableDeps, MissingDeps} = find_deps(find, Deps), %% For each missing dep with a specified source, try to pull it. - PulledDeps0 = [use_source(D) || D <- MissingDeps, D#dep.source /= undefined], - - %% For each available dep try to update the source to the specified - %% version. - PulledDeps1 = [update_source(D) || D <- AvailableDeps, - D#dep.source /= undefined], + PulledDeps = [use_source(D) || D <- MissingDeps, D#dep.source /= undefined], %% Add each pulled dep to our list of dirs for post-processing. This yields %% the necessary transitivity of the deps - erlang:put(?MODULE, [D#dep.dir || D <- PulledDeps0 ++ PulledDeps1]), + erlang:put(?MODULE, [D#dep.dir || D <- PulledDeps]), + ok. + +'update-deps'(Config, _) -> + %% Determine what deps are available and missing + Deps = rebar_config:get_local(Config, deps, []), + UpdatedDeps = [update_source(D) || D <- find_deps(read, Deps), + D#dep.source /= undefined], + %% Add each updated dep to our list of dirs for post-processing. This yields + %% the necessary transitivity of the deps + erlang:put(?MODULE, [D#dep.dir || D <- UpdatedDeps]), ok. 'delete-deps'(Config, _) -> %% Delete all the available deps in our deps/ directory, if any DepsDir = get_deps_dir(), Deps = rebar_config:get_local(Config, deps, []), - {AvailableDeps, _} = find_deps(Deps), - _ = [delete_dep(D) || D <- AvailableDeps, - lists:prefix(DepsDir, D#dep.dir) == true], + {AvailableDeps, _} = find_deps(find, Deps), + _ = [delete_dep(D) + || D <- AvailableDeps, + lists:prefix(DepsDir, D#dep.dir)], ok. @@ -141,7 +148,8 @@ compile(Config, AppFile) -> %% need all deps in same dir and should be the one set by the root rebar.config %% Sets a default if root config has no deps_dir set set_global_deps_dir(Config, []) -> - rebar_config:set_global(deps_dir, rebar_config:get_local(Config, deps_dir, "deps")); + rebar_config:set_global(deps_dir, + rebar_config:get_local(Config, deps_dir, "deps")); set_global_deps_dir(_Config, _DepsDir) -> ok. @@ -163,35 +171,48 @@ update_deps_code_path([Dep | Rest]) -> end, update_deps_code_path(Rest). -find_deps(Deps) -> - find_deps(Deps, {[], []}). -find_deps([], {Avail, Missing}) -> +find_deps(find=Mode, Deps) -> + find_deps(Mode, Deps, {[], []}); +find_deps(read=Mode, Deps) -> + find_deps(Mode, Deps, []). + +find_deps(find, [], {Avail, Missing}) -> {lists:reverse(Avail), lists:reverse(Missing)}; -find_deps([App | Rest], Acc) when is_atom(App) -> - find_deps([{App, ".*", undefined} | Rest], Acc); -find_deps([{App, VsnRegex} | Rest], Acc) when is_atom(App) -> - find_deps([{App, VsnRegex, undefined} | Rest], Acc); -find_deps([{App, VsnRegex, Source} | Rest], {Avail, Missing}) -> +find_deps(read, [], Deps) -> + lists:reverse(Deps); +find_deps(Mode, [App | Rest], Acc) when is_atom(App) -> + find_deps(Mode, [{App, ".*", undefined} | Rest], Acc); +find_deps(Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) -> + find_deps(Mode, [{App, VsnRegex, undefined} | Rest], Acc); +find_deps(Mode, [{App, VsnRegex, Source} | Rest], Acc) -> Dep = #dep { app = App, vsn_regex = VsnRegex, source = Source }, case is_app_available(App, VsnRegex) of {true, AppDir} -> - find_deps(Rest, {[Dep#dep { dir = AppDir } | Avail], Missing}); + find_deps(Mode, Rest, acc_deps(Mode, avail, Dep, AppDir, Acc)); {false, _} -> AppDir = filename:join(get_deps_dir(), Dep#dep.app), case is_app_available(App, VsnRegex, AppDir) of {true, AppDir} -> - find_deps(Rest, {[Dep#dep { dir = AppDir } | Avail], Missing}); + find_deps(Mode, Rest, + acc_deps(Mode, avail, Dep, AppDir, Acc)); {false, _} -> - find_deps(Rest, {Avail, [Dep#dep { dir = AppDir } | Missing]}) + find_deps(Mode, Rest, + acc_deps(Mode, missing, Dep, AppDir, Acc)) end end; -find_deps([Other | _Rest], _Acc) -> +find_deps(_Mode, [Other | _Rest], _Acc) -> ?ABORT("Invalid dependency specification ~p in ~s\n", [Other, rebar_utils:get_cwd()]). +acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) -> + {[Dep#dep { dir = AppDir } | Avail], Missing}; +acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) -> + {Avail, [Dep#dep { dir = AppDir } | Missing]}; +acc_deps(read, _, Dep, AppDir, Acc) -> + [Dep#dep { dir = AppDir } | Acc]. delete_dep(D) -> case filelib:is_dir(D#dep.dir) of @@ -228,28 +249,34 @@ is_app_available(App, VsnRegex, Path) -> nomatch -> ?WARN("~s has version ~p; requested regex was ~s\n", [AppFile, Vsn, VsnRegex]), - {false, version_mismatch} + {false, {version_mismatch, + {AppFile, + {expected, VsnRegex}, {has, Vsn}}}} end; OtherApp -> - ?WARN("~s has application id ~p; expected ~p\n", [AppFile, OtherApp, App]), - {false, name_mismatch} + ?WARN("~s has application id ~p; expected ~p\n", + [AppFile, OtherApp, App]), + {false, {name_mismatch, + {AppFile, {expected, App}, {has, OtherApp}}}} end; false -> - ?WARN("Expected ~s to be an app dir (containing ebin/*.app), but no .app found.\n", - [Path]), - {false, missing_app_file} + ?WARN("Expected ~s to be an app dir (containing ebin/*.app), " + "but no .app found.\n", [Path]), + {false, {missing_app_file, Path}} end. use_source(Dep) -> use_source(Dep, 3). use_source(Dep, 0) -> - ?ABORT("Failed to acquire source from ~p after 3 tries.\n", [Dep#dep.source]); + ?ABORT("Failed to acquire source from ~p after 3 tries.\n", + [Dep#dep.source]); use_source(Dep, Count) -> case filelib:is_dir(Dep#dep.dir) of true -> - %% Already downloaded -- verify the versioning matches up with our regex - case is_app_available(Dep#dep.app, Dep#dep.vsn_regex, Dep#dep.dir) of + %% Already downloaded -- verify the versioning matches the regex + case is_app_available(Dep#dep.app, + Dep#dep.vsn_regex, Dep#dep.dir) of {true, _} -> Dir = filename:join(Dep#dep.dir, "ebin"), ok = filelib:ensure_dir(filename:join(Dir, "dummy")), @@ -261,7 +288,7 @@ use_source(Dep, Count) -> %% The app that was downloaded doesn't match up (or had %% errors or something). For the time being, abort. ?ABORT("Dependency dir ~s failed application validation " - "with reason ~p.\n", [Dep#dep.dir, Reason]) + "with reason:~n~p.\n", [Dep#dep.dir, Reason]) end; false -> ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]), @@ -273,28 +300,35 @@ use_source(Dep, Count) -> download_source(AppDir, {hg, Url, Rev}) -> ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), [], filename:dirname(AppDir)), - rebar_utils:sh(?FMT("hg update ~s", [Rev]), [], AppDir); + rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]); +download_source(AppDir, {git, Url}) -> + download_source(AppDir, {git, Url, "HEAD"}); +download_source(AppDir, {git, Url, ""}) -> + download_source(AppDir, {git, Url, "HEAD"}); download_source(AppDir, {git, Url, {branch, Branch}}) -> ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), [], filename:dirname(AppDir)), - rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [], AppDir); + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]); download_source(AppDir, {git, Url, {tag, Tag}}) -> ok = filelib:ensure_dir(AppDir), - rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), [], filename:dirname(AppDir)), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [], AppDir); + rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]), + rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]); download_source(AppDir, {git, Url, Rev}) -> download_source(AppDir, {git, Url, {branch, Rev}}); download_source(AppDir, {bzr, Url, Rev}) -> ok = filelib:ensure_dir(AppDir), rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s", - [Rev, Url, filename:basename(AppDir)]), [], - filename:dirname(AppDir)); + [Rev, Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]); download_source(AppDir, {svn, Url, Rev}) -> ok = filelib:ensure_dir(AppDir), rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s", - [Rev, Url, filename:basename(AppDir)]), [], - filename:dirname(AppDir)). + [Rev, Url, filename:basename(AppDir)]), + [{cd, filename:dirname(AppDir)}]). update_source(Dep) -> %% It's possible when updating a source, that a given dep does not have a @@ -309,24 +343,31 @@ update_source(Dep) -> update_source(AppDir, Dep#dep.source), Dep; false -> - ?WARN("Skipping update for ~p: no VCS directory available!\n", [Dep]), + ?WARN("Skipping update for ~p: " + "no VCS directory available!\n", [Dep]), Dep end. +update_source(AppDir, {git, Url}) -> + update_source(AppDir, {git, Url, "HEAD"}); +update_source(AppDir, {git, Url, ""}) -> + update_source(AppDir, {git, Url, "HEAD"}); update_source(AppDir, {git, _Url, {branch, Branch}}) -> - rebar_utils:sh(?FMT("git fetch origin", []), [], AppDir), - rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [], AppDir); + ShOpts = [{cd, AppDir}], + rebar_utils:sh("git fetch origin", ShOpts), + rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), ShOpts); update_source(AppDir, {git, _Url, {tag, Tag}}) -> - rebar_utils:sh(?FMT("git fetch --tags origin", []), [], AppDir), - rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [], AppDir); + ShOpts = [{cd, AppDir}], + rebar_utils:sh("git fetch --tags origin", ShOpts), + rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts); update_source(AppDir, {git, Url, Refspec}) -> update_source(AppDir, {git, Url, {branch, Refspec}}); update_source(AppDir, {svn, _Url, Rev}) -> - rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [], AppDir); + rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]); update_source(AppDir, {hg, _Url, Rev}) -> - rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [], AppDir); + rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]); update_source(AppDir, {bzr, _Url, Rev}) -> - rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [], AppDir). + rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]). @@ -347,7 +388,8 @@ source_engine_avail({Name, _, _}=Source) scm_client_vsn(false, _VsnArg, _VsnRegex) -> false; scm_client_vsn(Path, VsnArg, VsnRegex) -> - Info = os:cmd("LANG=C " ++ Path ++ VsnArg), + {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]}, + {use_stdout, false}]), case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of {match, Match} -> list_to_tuple([list_to_integer(S) || S <- Match]); @@ -361,13 +403,17 @@ required_scm_client_vsn(bzr) -> {2, 0}; required_scm_client_vsn(svn) -> {1, 6}. scm_client_vsn(hg) -> - scm_client_vsn(rebar_utils:find_executable("hg"), " --version", "version (\\d+).(\\d+)"); + scm_client_vsn(rebar_utils:find_executable("hg"), " --version", + "version (\\d+).(\\d+)"); scm_client_vsn(git) -> - scm_client_vsn(rebar_utils:find_executable("git"), " --version", "git version (\\d+).(\\d+)"); + scm_client_vsn(rebar_utils:find_executable("git"), " --version", + "git version (\\d+).(\\d+)"); scm_client_vsn(bzr) -> - scm_client_vsn(rebar_utils:find_executable("bzr"), " --version", "Bazaar \\(bzr\\) (\\d+).(\\d+)"); + scm_client_vsn(rebar_utils:find_executable("bzr"), " --version", + "Bazaar \\(bzr\\) (\\d+).(\\d+)"); scm_client_vsn(svn) -> - scm_client_vsn(rebar_utils:find_executable("svn"), " --version", "svn, version (\\d+).(\\d+)"). + scm_client_vsn(rebar_utils:find_executable("svn"), " --version", + "svn, version (\\d+).(\\d+)"). has_vcs_dir(git, Dir) -> filelib:is_dir(filename:join(Dir, ".git")); diff --git a/src/rebar_dialyzer.erl b/src/rebar_dialyzer.erl index 9d45445..c3e3c36 100644 --- a/src/rebar_dialyzer.erl +++ b/src/rebar_dialyzer.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -31,15 +31,19 @@ %% <li>build-plt (essentially "dialyzer --build_plt -r <app_dirs>")</li> %% <li>check-plt (essentially "dialyzer --check_plt")</li> %% </ul> -%% A single option <code>plt</code> can be presented in the <code>dialyzer_opts</code> -%% options in <code>rebar.config</code>. If it is present, it is used as the PLT for the -%% supported commands. Should it not be present, then the default is <code>$HOME/.dialyzer_plt</code>. -%% @reference <a href="http://user.it.uu.se/~kostis/Papers/bugs05.pdf">Experience from developing the Dialyzer: -%% A static analysis tool detecting defects in Erlang applications</a> -%% @reference <a href="http://user.it.uu.se/~kostis/Papers/contracts.pdf">A Language for Specifying Type -%% Contracts in Erlang and its Interaction with Success Typings</a> -%% @reference <a href="http://user.it.uu.se/~kostis/Papers/wrangler.pdf">Gradual Typing of Erlang -%% Programs: A Wrangler Experience</a> +%% A single option <code>plt</code> can be presented in the +%% <code>dialyzer_opts</code> options in <code>rebar.config</code>. If it +%% is present, it is used as the PLT for the supported commands. Should it +%% not be present, then the default is <code>$HOME/.dialyzer_plt</code>. +%% +%% @reference <a href="http://user.it.uu.se/~kostis/Papers/bugs05.pdf"> +%% Experience from developing the Dialyzer: A static analysis tool detecting +%% defects in Erlang applications</a> +%% @reference <a href="http://user.it.uu.se/~kostis/Papers/contracts.pdf"> +%% A Language for Specifying Type Contracts in Erlang and its Interaction +%% with Success Typings</a> +%% @reference <a href="http://user.it.uu.se/~kostis/Papers/wrangler.pdf">Gradual +%% Typing of Erlang Programs: A Wrangler Experience</a> %% @copyright 2010 Dave Smith %% ------------------------------------------------------------------- -module(rebar_dialyzer). @@ -67,12 +71,12 @@ dialyze(Config, File) -> dialyzer_opts, [])), DialyzerOpts0 = case FromSrc of - true -> - [{files_rec, ["src"]}, {init_plt, Plt}, - {from, src_code}]; - false -> - [{files_rec, ["ebin"]}, {init_plt, Plt}] - end, + true -> + [{files_rec, ["src"]}, {init_plt, Plt}, + {from, src_code}]; + false -> + [{files_rec, ["ebin"]}, {init_plt, Plt}] + end, WarnOpts = warnings(Config), DialyzerOpts = case WarnOpts of [] -> DialyzerOpts0; @@ -150,7 +154,7 @@ app_dirs(Apps) -> -spec output_warnings(Warnings::[warning()]) -> 'ok'. output_warnings(Warnings) -> lists:foreach(fun(Warning) -> - ?CONSOLE("~s", [dialyzer:format_warning(Warning)]) + ?CONSOLE("~s", [dialyzer:format_warning(Warning)]) end, Warnings). %% @doc If the plt option is present in rebar.config return its value, otherwise @@ -193,7 +197,7 @@ existing_plt_path(Config, File) -> ?ABORT("No PLT found~n", []) end end; - [$~|[$/|Plt]] -> + "~/" ++ Plt -> filename:join(Home,Plt); Plt -> Plt diff --git a/src/rebar_edoc.erl b/src/rebar_edoc.erl index dd7e779..8eda04d 100644 --- a/src/rebar_edoc.erl +++ b/src/rebar_edoc.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -27,10 +27,12 @@ %% @author Dave Smith <dizzyd@dizzyd.com> %% @doc rebar_edoc supports the following command: %% <ul> -%% <li>doc (essentially erl -noshell -run edoc_run application "'$(<app_name>)'" +%% <li>doc (essentially erl -noshell -run edoc_run application +%% "'$(<app_name>)'" %% '"."' '[<options>]')</li> %% </ul> -%% EDoc options can be given in the <code>edoc_opts</code> option in <code>rebar.config</code>. +%% EDoc options can be given in the <code>edoc_opts</code> option in +%% <code>rebar.config</code>. %% @copyright 2010 Dave Smith %% ------------------------------------------------------------------- -module(rebar_edoc). diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index e69dea9..df9e1c0 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -42,34 +42,47 @@ %% %% * erl_opts - Erlang list of options passed to compile:file/2 %% It is also possible to specify platform specific -%% options by specifying a triplet where the first string -%% is a regex that is checked against Erlang's system -%% architecture string. E.g. to define HAVE_SENDFILE only -%% on systems with sendfile() and define BACKLOG on -%% Linux/FreeBSD as 128 do: +%% options by specifying a pair or a triplet where the +%% first string is a regex that is checked against the +%% string +%% +%% OtpRelease ++ "-" ++ SysArch ++ "-" ++ Words. +%% +%% where +%% +%% OtpRelease = erlang:system_info(otp_release). +%% SysArch = erlang:system_info(system_architecture). +%% Words = integer_to_list(8 * erlang:system_info(wordsize)). +%% +%% E.g. to define HAVE_SENDFILE only on systems with +%% sendfile(), to define BACKLOG on Linux/FreeBSD as 128, +%% and to define 'old_inets' for R13 OTP release do: +%% %% {erl_opts, [{platform_define, %% "(linux|solaris|freebsd|darwin)", %% 'HAVE_SENDFILE'}, %% {platform_define, "(linux|freebsd)", -%% 'BACKLOG', 128}]}. +%% 'BACKLOG', 128}, +%% {platform_define, "R13", +%% 'old_inets'}]}. %% -spec compile(Config::#config{}, AppFile::string()) -> 'ok'. compile(Config, _AppFile) -> rebar_base_compiler:run(Config, - check_files(rebar_config:get_local(Config, - xrl_first_files, [])), + check_files(rebar_config:get_local( + Config, xrl_first_files, [])), "src", ".xrl", "src", ".erl", fun compile_xrl/3), rebar_base_compiler:run(Config, - check_files(rebar_config:get_local(Config, - yrl_first_files, [])), + check_files(rebar_config:get_local( + Config, yrl_first_files, [])), "src", ".yrl", "src", ".erl", fun compile_yrl/3), doterl_compile(Config, "ebin"), rebar_base_compiler:run(Config, - check_files(rebar_config:get_local(Config, - mib_first_files, [])), + check_files(rebar_config:get_local( + Config, mib_first_files, [])), "mibs", ".mib", "priv/mibs", ".bin", fun compile_mib/3). @@ -119,26 +132,27 @@ doterl_compile(Config, OutDir, MoreSources) -> RestErls = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources, not lists:member(Source, FirstErls)], - % Split RestErls so that parse_transforms and behaviours are instead added - % to erl_first_files, parse transforms first. - % This should probably be somewhat combined with inspect_epp - [ParseTransforms, Behaviours, OtherErls] = lists:foldl(fun(F, [A, B, C]) -> - case compile_priority(F) of - parse_transform -> - [[F | A], B, C]; - behaviour -> - [A, [F | B], C]; - _ -> - [A, B, [F | C]] - end - end, [[], [], []], RestErls), + %% Split RestErls so that parse_transforms and behaviours are instead added + %% to erl_first_files, parse transforms first. + %% This should probably be somewhat combined with inspect_epp + [ParseTransforms, Behaviours, OtherErls] = + lists:foldl(fun(F, [A, B, C]) -> + case compile_priority(F) of + parse_transform -> + [[F | A], B, C]; + behaviour -> + [A, [F | B], C]; + _ -> + [A, B, [F | C]] + end + end, [[], [], []], RestErls), NewFirstErls = FirstErls ++ ParseTransforms ++ Behaviours, %% Make sure that ebin/ exists and is on the path ok = filelib:ensure_dir(filename:join("ebin", "dummy.beam")), CurrPath = code:get_path(), - true = code:add_path("ebin"), + true = code:add_path(filename:absname("ebin")), rebar_base_compiler:run(Config, NewFirstErls, OtherErls, fun(S, C) -> internal_erl_compile(S, C, OutDir, ErlOpts) @@ -151,12 +165,14 @@ doterl_compile(Config, OutDir, MoreSources) -> %% Internal functions %% =================================================================== --spec include_path(Source::string(), Config::#config{}) -> [string()]. +-spec include_path(Source::string(), Config::#config{}) -> [string(), ...]. include_path(Source, Config) -> ErlOpts = rebar_config:get(Config, erl_opts, []), - ["include", filename:dirname(Source)] ++ proplists:get_all_values(i, ErlOpts). + ["include", filename:dirname(Source)] + ++ proplists:get_all_values(i, ErlOpts). --spec inspect(Source::string(), IncludePath::[string(),...]) -> {string(), [string()]}. +-spec inspect(Source::string(), + IncludePath::[string(),...]) -> {string(), [string()]}. inspect(Source, IncludePath) -> ModuleDefault = filename:basename(Source, ".erl"), case epp:open(Source, IncludePath) of @@ -167,7 +183,8 @@ inspect(Source, IncludePath) -> {ModuleDefault, []} end. --spec inspect_epp(Epp::pid(), Source::string(), Module::string(), Includes::[string()]) -> {string(), [string()]}. +-spec inspect_epp(Epp::pid(), Source::string(), Module::string(), + Includes::[string()]) -> {string(), [string()]}. inspect_epp(Epp, Source, Module, Includes) -> case epp:parse_erl_form(Epp) of {ok, {attribute, _, module, ModInfo}} -> @@ -177,13 +194,15 @@ inspect_epp(Epp, Source, Module, Includes) -> ActualModuleStr = atom_to_list(ActualModule); %% Packag-ized module name, list of atoms ActualModule when is_list(ActualModule) -> - ActualModuleStr = string:join([atom_to_list(P) || P <- ActualModule], "."); + ActualModuleStr = string:join([atom_to_list(P) || + P <- ActualModule], "."); %% Parameterized module name, single atom {ActualModule, _} when is_atom(ActualModule) -> ActualModuleStr = atom_to_list(ActualModule); %% Parameterized and packagized module name, list of atoms {ActualModule, _} when is_list(ActualModule) -> - ActualModuleStr = string:join([atom_to_list(P) || P <- ActualModule], ".") + ActualModuleStr = string:join([atom_to_list(P) || + P <- ActualModule], ".") end, inspect_epp(Epp, Source, ActualModuleStr, Includes); {ok, {attribute, 1, file, {Module, 1}}} -> @@ -199,14 +218,16 @@ inspect_epp(Epp, Source, Module, Includes) -> inspect_epp(Epp, Source, Module, Includes) end. --spec needs_compile(Source::string(), Target::string(), Hrls::[string()]) -> boolean(). +-spec needs_compile(Source::string(), Target::string(), + Hrls::[string()]) -> boolean(). needs_compile(Source, Target, Hrls) -> TargetLastMod = filelib:last_modified(Target), lists:any(fun(I) -> TargetLastMod < filelib:last_modified(I) end, [Source] ++ Hrls). -spec internal_erl_compile(Source::string(), Config::#config{}, - Outdir::string(), ErlOpts::list()) -> 'ok' | 'skipped'. + Outdir::string(), + ErlOpts::list()) -> 'ok' | 'skipped'. internal_erl_compile(Source, Config, Outdir, ErlOpts) -> %% Determine the target name and includes list by inspecting the source file {Module, Hrls} = inspect(Source, include_path(Source, Config)), @@ -219,15 +240,18 @@ internal_erl_compile(Source, Config, Outdir, ErlOpts) -> %% the target, case needs_compile(Source, Target, Hrls) of true -> - Opts = [{i, "include"}, {outdir, filename:dirname(Target)}, report, return] ++ - ErlOpts, + Opts = [{outdir, filename:dirname(Target)}] ++ + ErlOpts ++ [{i, "include"}, report, return], case compile:file(Source, Opts) of {ok, _, []} -> ok; {ok, _, _Warnings} -> - %% We got at least one warning -- if fail_on_warning is in options, fail + %% We got at least one warning -- if fail_on_warning + %% is in options, fail case lists:member(fail_on_warning, Opts) of true -> + %% remove target to prevent overlooking this failure + ok = file:delete(Target), ?FAIL; false -> ok @@ -239,7 +263,8 @@ internal_erl_compile(Source, Config, Outdir, ErlOpts) -> skipped end. --spec compile_mib(Source::string(), Target::string(), Config::#config{}) -> 'ok'. +-spec compile_mib(Source::string(), Target::string(), + Config::#config{}) -> 'ok'. compile_mib(Source, Target, Config) -> ok = rebar_utils:ensure_dir(Target), Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++ @@ -251,13 +276,15 @@ compile_mib(Source, Target, Config) -> ?FAIL end. --spec compile_xrl(Source::string(), Target::string(), Config::#config{}) -> 'ok'. +-spec compile_xrl(Source::string(), Target::string(), + Config::#config{}) -> 'ok'. compile_xrl(Source, Target, Config) -> Opts = [{scannerfile, Target}, {return, true} |rebar_config:get(Config, xrl_opts, [])], compile_xrl_yrl(Source, Target, Opts, leex). --spec compile_yrl(Source::string(), Target::string(), Config::#config{}) -> 'ok'. +-spec compile_yrl(Source::string(), Target::string(), + Config::#config{}) -> 'ok'. compile_yrl(Source, Target, Config) -> Opts = [{parserfile, Target}, {return, true} |rebar_config:get(Config, yrl_opts, [])], @@ -300,7 +327,8 @@ src_dirs(SrcDirs) -> dirs(Dir) -> [F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)]. --spec delete_dir(Dir::string(), Subdirs::[string()]) -> 'ok' | {'error', atom()}. +-spec delete_dir(Dir::string(), + Subdirs::[string()]) -> 'ok' | {'error', atom()}. delete_dir(Dir, []) -> file:del_dir(Dir); delete_dir(Dir, Subdirs) -> @@ -315,23 +343,24 @@ compile_priority(File) -> normal; % couldn't parse the file, default priority {ok, Trees} -> F2 = fun({tree,arity_qualifier,_, - {arity_qualifier,{tree,atom,_,behaviour_info}, - {tree,integer,_,1}}}, _) -> - behaviour; - ({tree,arity_qualifier,_, - {arity_qualifier,{tree,atom,_,parse_transform}, - {tree,integer,_,2}}}, _) -> - parse_transform; - (_, Acc) -> - Acc - end, - - F = fun({tree, attribute, _, {attribute, {tree, atom, _, export}, - [{tree, list, _, {list, List, none}}]}}, Acc) -> - lists:foldl(F2, Acc, List); - (_, Acc) -> - Acc - end, + {arity_qualifier,{tree,atom,_,behaviour_info}, + {tree,integer,_,1}}}, _) -> + behaviour; + ({tree,arity_qualifier,_, + {arity_qualifier,{tree,atom,_,parse_transform}, + {tree,integer,_,2}}}, _) -> + parse_transform; + (_, Acc) -> + Acc + end, + + F = fun({tree, attribute, _, + {attribute, {tree, atom, _, export}, + [{tree, list, _, {list, List, none}}]}}, Acc) -> + lists:foldl(F2, Acc, List); + (_, Acc) -> + Acc + end, lists:foldl(F, normal, Trees) end. diff --git a/src/rebar_erlydtl_compiler.erl b/src/rebar_erlydtl_compiler.erl index b3cbd98..f17e8fd 100644 --- a/src/rebar_erlydtl_compiler.erl +++ b/src/rebar_erlydtl_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -83,8 +83,10 @@ compile(Config, _AppFile) -> DtlOpts = erlydtl_opts(Config), rebar_base_compiler:run(Config, [], - option(doc_root, DtlOpts), option(source_ext, DtlOpts), - option(out_dir, DtlOpts), option(module_ext, DtlOpts) ++ ".beam", + option(doc_root, DtlOpts), + option(source_ext, DtlOpts), + option(out_dir, DtlOpts), + option(module_ext, DtlOpts) ++ ".beam", fun compile_dtl/3, [{check_last_mod, false}]). @@ -102,18 +104,18 @@ default(doc_root) -> "templates"; default(out_dir) -> "ebin"; default(source_ext) -> ".dtl"; default(module_ext) -> "_dtl"; -default(custom_tags_dir) -> "". +default(custom_tags_dir) -> "". compile_dtl(Source, Target, Config) -> case code:which(erlydtl) of non_existing -> ?CONSOLE( - "~n===============================================~n" - " You need to install erlydtl to comple DTL templates~n" - " Download the latest tarball release from github~n" - " http://code.google.com/p/erlydtl/~n" - " and install it into your erlang library dir~n" - "===============================================~n~n", []), + <<"~n===============================================~n" + " You need to install erlydtl to compile DTL templates~n" + " Download the latest tarball release from github~n" + " http://code.google.com/p/erlydtl/~n" + " and install it into your erlang library dir~n" + "===============================================~n~n">>, []), ?FAIL; _ -> case needs_compile(Source, Target, Config) of @@ -162,18 +164,26 @@ referenced_dtls1(Step, Config, Seen) -> DtlOpts = erlydtl_opts(Config), ExtMatch = re:replace(option(source_ext, DtlOpts), "\.", "\\\\\\\\.", [{return, list}]), - AllRefs = lists:append( - [string:tokens( - os:cmd(["grep -o [^\\\"]*",ExtMatch," ",F]), - "\n") - || F <- Step]), + + ShOpts = [{use_stdout, false}, return_on_error], + AllRefs = + lists:append( + [begin + Cmd = lists:flatten(["grep -o [^\\\"]*", + ExtMatch, " ", F]), + case rebar_utils:sh(Cmd, ShOpts) of + {ok, Res} -> + string:tokens(Res, "\n"); + {error, _} -> + "" + end + end || F <- Step]), DocRoot = option(doc_root, DtlOpts), WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ], - Existing = [F || F <- WithPaths, filelib:is_file(F)], + Existing = [F || F <- WithPaths, filelib:is_regular(F)], New = sets:subtract(sets:from_list(Existing), Seen), case sets:size(New) of 0 -> Seen; _ -> referenced_dtls1(sets:to_list(New), Config, sets:union(New, Seen)) end. - diff --git a/src/rebar_escripter.erl b/src/rebar_escripter.erl index f2870d3..8b23263 100644 --- a/src/rebar_escripter.erl +++ b/src/rebar_escripter.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -30,6 +30,7 @@ clean/2]). -include("rebar.hrl"). +-include_lib("kernel/include/file.hrl"). %% =================================================================== %% Public API @@ -47,7 +48,8 @@ escriptize(Config, AppFile) -> %% Look for a list of other applications (dependencies) to include %% in the output file. We then use the .app files for each of these %% to pull in all the .beam files. - InclBeams = get_app_beams(rebar_config:get_local(Config, escript_incl_apps, []), []), + InclBeams = get_app_beams( + rebar_config:get_local(Config, escript_incl_apps, []), []), %% Construct the archive of everything in ebin/ dir -- put it on the %% top-level of the zip file so that code loading works properly. @@ -61,16 +63,19 @@ escriptize(Config, AppFile) -> ok -> ok; {error, WriteError} -> - ?ERROR("Failed to write ~p script: ~p\n", [AppName, WriteError]), + ?ERROR("Failed to write ~p script: ~p\n", + [AppName, WriteError]), ?FAIL end; {error, ZipError} -> - ?ERROR("Failed to construct ~p escript: ~p\n", [AppName, ZipError]), + ?ERROR("Failed to construct ~p escript: ~p\n", + [AppName, ZipError]), ?FAIL end, %% Finally, update executable perms for our script - [] = os:cmd(?FMT("chmod u+x ~p", [Filename])), + {ok, #file_info{mode = Mode}} = file:read_file_info(Filename), + ok = file:change_mode(Filename, Mode bor 8#00100), ok. clean(Config, AppFile) -> @@ -92,9 +97,11 @@ get_app_beams([], Acc) -> get_app_beams([App | Rest], Acc) -> case code:lib_dir(App, ebin) of {error, bad_name} -> - ?ABORT("Failed to get ebin/ directory for ~p escript_incl_apps.", [App]); + ?ABORT("Failed to get ebin/ directory for " + "~p escript_incl_apps.", [App]); Path -> - Acc2 = [{filename:join([App, ebin, F]), file_contents(filename:join(Path, F))} || + Acc2 = [{filename:join([App, ebin, F]), + file_contents(filename:join(Path, F))} || F <- filelib:wildcard("*", Path)], get_app_beams(Rest, Acc2 ++ Acc) end. diff --git a/src/rebar_eunit.erl b/src/rebar_eunit.erl index 1341f9d..aee487c 100644 --- a/src/rebar_eunit.erl +++ b/src/rebar_eunit.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -36,15 +36,15 @@ %% <li>suite="foo"" - runs test/foo_tests.erl</li> %% </ul> %% Additionally, for projects that have separate folders for the core -%% implementation, and for the unit tests, then the following <code>rebar.config</code> -%% option can be provided: <code>{eunit_compile_opts, [{src_dirs, ["dir"]}]}.</code>. +%% implementation, and for the unit tests, then the following +%% <code>rebar.config</code> option can be provided: +%% <code>{eunit_compile_opts, [{src_dirs, ["dir"]}]}.</code>. %% @copyright 2009, 2010 Dave Smith %% ------------------------------------------------------------------- -module(rebar_eunit). --export([eunit/2]). - --compile([export_all]). +-export([eunit/2, + clean/2]). -include("rebar.hrl"). @@ -78,16 +78,17 @@ eunit(Config, AppFile) -> ok = filelib:ensure_dir(eunit_dir() ++ "/foo"), ok = filelib:ensure_dir(ebin_dir() ++ "/foo"), - %% Setup code path prior to compilation so that parse_transforms and the like - %% work properly. Also, be sure to add ebin_dir() to the END of the code path - %% so that we don't have to jump through hoops to access the .app file + %% Setup code path prior to compilation so that parse_transforms + %% and the like work properly. Also, be sure to add ebin_dir() + %% to the END of the code path so that we don't have to jump + %% through hoops to access the .app file CodePath = code:get_path(), true = code:add_patha(eunit_dir()), true = code:add_pathz(ebin_dir()), %% Obtain all the test modules for inclusion in the compile stage. - %% Notice: this could also be achieved with the following rebar.config option: - %% {eunit_compile_opts, [{src_dirs, ["test"]}]} + %% Notice: this could also be achieved with the following + %% rebar.config option: {eunit_compile_opts, [{src_dirs, ["test"]}]} TestErls = rebar_utils:find_files("test", ".*\\.erl\$"), %% Copy source files to eunit dir for cover in case they are not directly @@ -99,7 +100,8 @@ eunit(Config, AppFile) -> %% Compile erlang code to ?EUNIT_DIR, using a tweaked config %% with appropriate defines for eunit, and include all the test modules %% as well. - rebar_erlc_compiler:doterl_compile(eunit_config(Config), ?EUNIT_DIR, TestErls), + rebar_erlc_compiler:doterl_compile(eunit_config(Config), + ?EUNIT_DIR, TestErls), %% Build a list of all the .beams in ?EUNIT_DIR -- use this for cover %% and eunit testing. Normally you can just tell cover and/or eunit to @@ -111,7 +113,7 @@ eunit(Config, AppFile) -> string:str(N, "_tests.beam") =:= 0], Modules = [rebar_utils:beam_to_mod(?EUNIT_DIR, N) || N <- BeamFiles], SrcModules = [rebar_utils:erl_to_mod(M) || M <- SrcErls], - + cover_init(Config, BeamFiles), EunitResult = perform_eunit(Config, Modules), perform_cover(Config, Modules, SrcModules), @@ -175,37 +177,47 @@ get_eunit_opts(Config) -> BaseOpts ++ rebar_config:get_list(Config, eunit_opts, []). eunit_config(Config) -> - EqcOpts = case is_quickcheck_avail() of - true -> - [{d, 'EQC'}]; - false -> - [] - end, + EqcOpts = eqc_opts(), + PropErOpts = proper_opts(), ErlOpts = rebar_config:get_list(Config, erl_opts, []), EunitOpts = rebar_config:get_list(Config, eunit_compile_opts, []), Opts = [{d, 'TEST'}, debug_info] ++ - ErlOpts ++ EunitOpts ++ EqcOpts, - rebar_config:set(Config, erl_opts, Opts). + ErlOpts ++ EunitOpts ++ EqcOpts ++ PropErOpts, + Config1 = rebar_config:set(Config, erl_opts, Opts), + + FirstErls = rebar_config:get_list(Config1, eunit_first_files, []), + rebar_config:set(Config1, erl_first_files, FirstErls). + + +eqc_opts() -> + define_if('PROPER', is_lib_avail(is_eqc_avail, eqc, + "eqc.hrl", "QuickCheck")). +proper_opts() -> + define_if('EQC', is_lib_avail(is_proper_avail, proper, + "proper.hrl", "PropEr")). + +define_if(Def, true) -> [{d, Def}]; +define_if(_Def, false) -> []. -is_quickcheck_avail() -> - case erlang:get(is_quickcheck_avail) of +is_lib_avail(DictKey, Mod, Hrl, Name) -> + case erlang:get(DictKey) of undefined -> - case code:lib_dir(eqc, include) of - {error, bad_name} -> - IsAvail = false; - Dir -> - IsAvail = filelib:is_file(filename:join(Dir, "eqc.hrl")) - end, - erlang:put(is_quickcheck_avail, IsAvail), - ?DEBUG("Quickcheck availability: ~p\n", [IsAvail]), + IsAvail = case code:lib_dir(Mod, include) of + {error, bad_name} -> + false; + Dir -> + filelib:is_regular(filename:join(Dir, Hrl)) + end, + erlang:put(DictKey, IsAvail), + ?DEBUG("~s availability: ~p\n", [Name, IsAvail]), IsAvail; IsAvail -> IsAvail end. perform_cover(Config, BeamFiles, SrcModules) -> - perform_cover(rebar_config:get(Config, cover_enabled, false), + perform_cover(rebar_config:get(Config, cover_enabled, false), Config, BeamFiles, SrcModules). perform_cover(false, _Config, _BeamFiles, _SrcModules) -> @@ -227,7 +239,8 @@ cover_analyze(Config, Modules, SrcModules) -> %% Write coverage details for each file lists:foreach(fun({M, _, _}) -> - {ok, _} = cover:analyze_to_file(M, cover_file(M), [html]) + {ok, _} = cover:analyze_to_file(M, cover_file(M), + [html]) end, Coverage), Index = filename:join([rebar_utils:get_cwd(), ?EUNIT_DIR, "index.html"]), @@ -260,7 +273,12 @@ cover_init(true, BeamFiles) -> %% It's not an error for cover compilation to fail partially, %% but we do want to warn about them - _ = [?CONSOLE("Cover compilation warning for ~p: ~p", [Beam, Desc]) || {Beam, {error, Desc}} <- Compiled], + PrintWarning = + fun(Beam, Desc) -> + ?CONSOLE("Cover compilation warning for ~p: ~p", + [Beam, Desc]) + end, + _ = [PrintWarning(Beam, Desc) || {Beam, {error, Desc}} <- Compiled], ok end; cover_init(Config, BeamFiles) -> @@ -287,18 +305,19 @@ is_eunitized(Mod) -> has_eunit_test_fun(Mod) -> [F || {exports, Funs} <- Mod:module_info(), - {F, 0} <- Funs, F =:= test] =/= []. + {F, 0} <- Funs, F =:= test] =/= []. has_header(Mod, Header) -> - Mod1 = case code:which(Mod) of - cover_compiled -> + Mod1 = case code:which(Mod) of + cover_compiled -> {file, File} = cover:is_compiled(Mod), File; non_existing -> Mod; preloaded -> Mod; L -> L end, - {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Mod1, [abstract_code]), + {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Mod1, + [abstract_code]), [F || {attribute, 1, file, {F, 1}} <- AC, string:str(F, Header) =/= 0] =/= []. @@ -310,7 +329,7 @@ align_notcovered_count(Module, Covered, NotCovered, true) -> cover_write_index(Coverage, SrcModules) -> {ok, F} = file:open(filename:join([?EUNIT_DIR, "index.html"]), [write]), ok = file:write(F, "<html><head><title>Coverage Summary</title></head>\n"), - IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, + IsSrcCoverage = fun({Mod,_C,_N}) -> lists:member(Mod, SrcModules) end, {SrcCoverage, TestCoverage} = lists:partition(IsSrcCoverage, Coverage), cover_write_index_section(F, "Source", SrcCoverage), cover_write_index_section(F, "Test", TestCoverage), @@ -331,9 +350,13 @@ cover_write_index_section(F, SectionName, Coverage) -> ok = file:write(F, ?FMT("<h3>Total: ~s</h3>\n", [TotalCoverage])), ok = file:write(F, "<table><tr><th>Module</th><th>Coverage %</th></tr>\n"), + FmtLink = + fun(Module, Cov, NotCov) -> + ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n", + [Module, Module, percentage(Cov, NotCov)]) + end, lists:foreach(fun({Module, Cov, NotCov}) -> - ok = file:write(F, ?FMT("<tr><td><a href='~s.COVER.html'>~s</a></td><td>~s</td>\n", - [Module, Module, percentage(Cov, NotCov)])) + ok = file:write(F, FmtLink(Module, Cov, NotCov)) end, Coverage), ok = file:write(F, "</table>\n"). @@ -345,13 +368,13 @@ cover_print_coverage(Coverage) -> %% Determine the longest module name for right-padding Width = lists:foldl(fun({Mod, _, _}, Acc) -> - case length(atom_to_list(Mod)) of - N when N > Acc -> - N; - _ -> - Acc - end - end, 0, Coverage) * -1, + case length(atom_to_list(Mod)) of + N when N > Acc -> + N; + _ -> + Acc + end + end, 0, Coverage) * -1, %% Print the output the console ?CONSOLE("~nCode Coverage:~n", []), diff --git a/src/rebar_file_utils.erl b/src/rebar_file_utils.erl index 1538fa8..731f9d9 100644 --- a/src/rebar_file_utils.erl +++ b/src/rebar_file_utils.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -28,6 +28,7 @@ -export([rm_rf/1, cp_r/2, + mv/2, delete_each/1]). -include("rebar.hrl"). @@ -42,25 +43,55 @@ -spec rm_rf(Target::string()) -> ok. rm_rf(Target) -> case os:type() of - {unix,_} -> - [] = os:cmd(?FMT("rm -rf ~s", [Target])), + {unix, _} -> + {ok, []} = rebar_utils:sh(?FMT("rm -rf ~s", [Target]), + [{use_stdout, false}, return_on_error]), ok; - {win32,_} -> - ok = rm_rf_win32(Target) + {win32, _} -> + Filelist = filelib:wildcard(Target), + Dirs = [F || F <- Filelist, filelib:is_dir(F)], + Files = Filelist -- Dirs, + ok = delete_each(Files), + ok = delete_each_dir_win32(Dirs), + ok end. -spec cp_r(Sources::list(string()), Dest::string()) -> ok. cp_r(Sources, Dest) -> case os:type() of - {unix,_} -> + {unix, _} -> SourceStr = string:join(Sources, " "), - [] = os:cmd(?FMT("cp -R ~s ~s", [SourceStr, Dest])), + {ok, []} = rebar_utils:sh(?FMT("cp -R ~s ~s", [SourceStr, Dest]), + [{use_stdout, false}, return_on_error]), ok; - {win32,_} -> + {win32, _} -> lists:foreach(fun(Src) -> ok = cp_r_win32(Src,Dest) end, Sources), ok end. +-spec mv(Source::string(), Dest::string()) -> ok. +mv(Source, Dest) -> + case os:type() of + {unix, _} -> + {ok, []} = rebar_utils:sh(?FMT("mv ~s ~s", [Source, Dest]), + [{use_stdout, false}, return_on_error]), + ok; + {win32, _} -> + {ok, R} = rebar_utils:sh( + ?FMT("cmd " "/c move /y ~s ~s 1> nul", + [filename:nativename(Source), + filename:nativename(Dest)]), + [{use_stdout, false}, return_on_error]), + case R of + [] -> + ok; + _ -> + {error, lists:flatten( + io_lib:format("Failed to move ~s to ~s~n", + [Source, Dest]))} + end + end. + delete_each([]) -> ok; delete_each([File | Rest]) -> @@ -78,51 +109,46 @@ delete_each([File | Rest]) -> %% Internal functions %% =================================================================== -rm_rf_win32(Target) -> - Filelist = filelib:wildcard(Target), - Dirs = lists:filter(fun filelib:is_dir/1,Filelist), - Files = lists:subtract(Filelist,Dirs), - ok = delete_each(Files), - ok = delete_each_dir_win32(Dirs), - ok. - delete_each_dir_win32([]) -> ok; delete_each_dir_win32([Dir | Rest]) -> - [] = os:cmd(?FMT("rd /q /s ~s", [filename:nativename(Dir)])), + {ok, []} = rebar_utils:sh(?FMT("cmd /c rd /q /s ~s", + [filename:nativename(Dir)]), + [{use_stdout, false}, return_on_error]), delete_each_dir_win32(Rest). xcopy_win32(Source,Dest)-> - R = os:cmd(?FMT("xcopy ~s ~s /q /y /e 2> nul", - [filename:nativename(Source), filename:nativename(Dest)])), - case string:str(R,"\r\n") > 0 of + {ok, R} = rebar_utils:sh( + ?FMT("cmd /c xcopy ~s ~s /q /y /e 2> nul", + [filename:nativename(Source), filename:nativename(Dest)]), + [{use_stdout, false}, return_on_error]), + case length(R) > 0 of %% when xcopy fails, stdout is empty and and error message is printed %% to stderr (which is redirected to nul) true -> ok; false -> {error, lists:flatten( - io_lib:format("Failed to xcopy from ~s to ~s\n", - [Source, Dest]))} + io_lib:format("Failed to xcopy from ~s to ~s~n", + [Source, Dest]))} end. -cp_r_win32({true,SourceDir},{true,DestDir}) -> - % from directory to directory +cp_r_win32({true, SourceDir}, {true, DestDir}) -> + %% from directory to directory SourceBase = filename:basename(SourceDir), - ok = case file:make_dir(filename:join(DestDir,SourceBase)) of - {error,eexist} -> ok; + ok = case file:make_dir(filename:join(DestDir, SourceBase)) of + {error, eexist} -> ok; Other -> Other end, - ok = xcopy_win32(SourceDir,filename:join(DestDir,SourceBase)); -cp_r_win32({false,Source},{true,DestDir}) -> - % from file to directory - cp_r_win32({false,Source}, - {false,filename:join(DestDir,filename:basename(Source))}); -cp_r_win32({false,Source},{false,Dest}) -> - % from file to file - {ok,_} = file:copy(Source,Dest), + ok = xcopy_win32(SourceDir, filename:join(DestDir, SourceBase)); +cp_r_win32({false, Source} = S,{true, DestDir}) -> + %% from file to directory + cp_r_win32(S, {false, filename:join(DestDir, filename:basename(Source))}); +cp_r_win32({false, Source},{false, Dest}) -> + %% from file to file + {ok,_} = file:copy(Source, Dest), ok; cp_r_win32(Source,Dest) -> - Dst = {filelib:is_dir(Dest),Dest}, + Dst = {filelib:is_dir(Dest), Dest}, lists:foreach(fun(Src) -> - ok = cp_r_win32({filelib:is_dir(Src),Src},Dst) + ok = cp_r_win32({filelib:is_dir(Src), Src}, Dst) end, filelib:wildcard(Source)), ok. diff --git a/src/rebar_lfe_compiler.erl b/src/rebar_lfe_compiler.erl index 4451530..b100e3d 100644 --- a/src/rebar_lfe_compiler.erl +++ b/src/rebar_lfe_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -49,12 +49,13 @@ compile(Config, _AppFile) -> compile_lfe(Source, _Target, Config) -> case code:which(lfe_comp) of non_existing -> - ?CONSOLE("~n===============================================~n" ++ - " You need to install LFE to compile LFE source~n" ++ - "Download the latest tarball release from github~n" ++ - " http://github.com/rvirding/lfe/downloads~n" ++ - " and install it into your erlang library dir~n" ++ - "===============================================~n~n", []), + ?CONSOLE( + <<"~n===============================================~n" + " You need to install LFE to compile LFE source files~n" + "Download the latest tarball release from github~n" + " https://github.com/rvirding/lfe/downloads~n" + " and install it into your erlang library dir~n" + "===============================================~n~n">>, []), ?FAIL; _ -> Opts = [{i, "include"}, {outdir, "ebin"}, report, return] ++ diff --git a/src/rebar_log.erl b/src/rebar_log.erl index dba59fe..54774d7 100644 --- a/src/rebar_log.erl +++ b/src/rebar_log.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -81,6 +81,3 @@ log_prefix(debug) -> "DEBUG: "; log_prefix(info) -> "INFO: "; log_prefix(warn) -> "WARN: "; log_prefix(error) -> "ERROR: ". - - - diff --git a/src/rebar_neotoma_compiler.erl b/src/rebar_neotoma_compiler.erl index 89cd089..e84ab2a 100644 --- a/src/rebar_neotoma_compiler.erl +++ b/src/rebar_neotoma_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -27,7 +27,7 @@ %% The rebar_neotoma module is a plugin for rebar that compiles %% neotoma peg files. By default, it compiles all src/*.peg to src/*.erl -%% +%% %% Configuration options should be placed in rebar.config under %% neotoma_opts. Available options include: %% @@ -52,9 +52,10 @@ compile(Config, _AppFile) -> NeoOpts = neotoma_opts(Config), rebar_base_compiler:run(Config, [], option(doc_root, NeoOpts), ".peg", - option(out_dir, NeoOpts), option(module_ext, NeoOpts) ++ ".beam", + option(out_dir, NeoOpts), + option(module_ext, NeoOpts) ++ ".beam", fun compile_neo/3, [{check_last_mod,false}]). - + %% ============================================================================ %% Public API %% ============================================================================ @@ -71,47 +72,48 @@ default(module_ext) -> ""; default(source_ext) -> ".peg". compile_neo(Source, Target, Config) -> - case code:which(neotoma) of - non_existing -> - ?CONSOLE( - "~n===============================================~n" - " You need to install neotoma to compile PEG grammars~n" - " Download the latest tarball release from github~n" - " http://github.com/seancribbs/neotoma~n" - " and install it into your erlang library dir~n" - "===============================================~n~n", []), - ?FAIL; - _ -> - case needs_compile(Source, Target, Config) of - true -> - do_compile(Source, Target, Config); - false -> - skipped - end - end. - + case code:which(neotoma) of + non_existing -> + ?CONSOLE( + <<"~n===============================================~n" + " You need to install neotoma to compile PEG grammars~n" + " Download the latest tarball release from github~n" + " https://github.com/seancribbs/neotoma~n" + " and install it into your erlang library dir~n" + "===============================================~n~n">>, []), + ?FAIL; + _ -> + case needs_compile(Source, Target, Config) of + true -> + do_compile(Source, Target, Config); + false -> + skipped + end + end. + do_compile(Source, _Target, Config) -> %% TODO: Check last mod on target and referenced DTLs here.. NeoOpts = neotoma_opts(Config), %% ensure that doc_root and out_dir are defined, %% using defaults if necessary Opts = [{output, option(out_dir, NeoOpts)}, - {module, list_to_atom(filename:basename(Source, ".peg") ++ option(module_ext, NeoOpts))}], + {module, list_to_atom(filename:basename(Source, ".peg") + ++ option(module_ext, NeoOpts))}], case neotoma:file(Source, Opts ++ NeoOpts) of - ok -> + ok -> ok; Reason -> ?CONSOLE("Compiling peg ~s failed:~n ~p~n", [Source, Reason]), ?FAIL end. - + needs_compile(Source, Target, Config) -> LM = filelib:last_modified(Target), LM < filelib:last_modified(Source) orelse lists:any(fun(D) -> LM < filelib:last_modified(D) end, referenced_pegs(Source, Config)). - + referenced_pegs(Source, Config) -> Set = referenced_pegs1([Source], Config, sets:add_element(Source, sets:new())), @@ -121,14 +123,23 @@ referenced_pegs1(Step, Config, Seen) -> NeoOpts = neotoma_opts(Config), ExtMatch = re:replace(option(source_ext, NeoOpts), "\.", "\\\\\\\\.", [{return, list}]), - AllRefs = lists:append( - [string:tokens( - os:cmd(["grep -o [^\\\"]*",ExtMatch," ",F]), - "\n") - || F <- Step]), + + ShOpts = [{use_stdout, false}, return_on_error], + AllRefs = + lists:append( + [begin + Cmd = lists:flatten(["grep -o [^\\\"]*", + ExtMatch, " ", F]), + case rebar_utils:sh(Cmd, ShOpts) of + {ok, Res} -> + string:tokens(Res, "\n"); + {error, _} -> + "" + end + end || F <- Step]), DocRoot = option(doc_root, NeoOpts), WithPaths = [ filename:join([DocRoot, F]) || F <- AllRefs ], - Existing = [F || F <- WithPaths, filelib:is_file(F)], + Existing = [F || F <- WithPaths, filelib:is_regular(F)], New = sets:subtract(sets:from_list(Existing), Seen), case sets:size(New) of 0 -> Seen; diff --git a/src/rebar_otp_app.erl b/src/rebar_otp_app.erl index a8a4ecd..f92de5c 100644 --- a/src/rebar_otp_app.erl +++ b/src/rebar_otp_app.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -40,11 +40,11 @@ compile(Config, File) -> %% written out as a ebin/*.app file. That resulting file will then %% be validated as usual. AppFile = case rebar_app_utils:is_app_src(File) of - true -> - preprocess(File); - false -> - File - end, + true -> + preprocess(Config, File); + false -> + File + end, %% Load the app file and validate it. case rebar_app_utils:load_app_file(AppFile) of @@ -52,11 +52,12 @@ compile(Config, File) -> validate_name(AppName, AppFile), %% In general, the list of modules is an important thing to validate - %% for compliance with OTP guidelines and upgrade procedures. However, - %% some people prefer not to validate this list. + %% for compliance with OTP guidelines and upgrade procedures. + %% However, some people prefer not to validate this list. case rebar_config:get_local(Config, validate_app_modules, true) of true -> - validate_modules(AppName, proplists:get_value(modules, AppData)); + validate_modules(AppName, + proplists:get_value(modules, AppData)); false -> ok end; @@ -86,48 +87,75 @@ clean(_Config, File) -> %% Internal functions %% =================================================================== -preprocess(AppSrcFile) -> +preprocess(Config, AppSrcFile) -> case rebar_app_utils:load_app_file(AppSrcFile) of {ok, AppName, AppData} -> - %% Get a list of all the modules available in ebin/ and update - %% the app data accordingly - A1 = lists:keystore(modules, 1, AppData, {modules, ebin_modules()}), + %% Look for a configuration file with vars we want to + %% substitute. Note that we include the list of modules available in + %% ebin/ and update the app data accordingly. + AppVars = load_app_vars(Config) ++ [{modules, ebin_modules()}], + A1 = apply_app_vars(AppVars, AppData), + + + %% AppSrcFile may contain instructions for generating a vsn number + Vsn = rebar_app_utils:app_vsn(AppSrcFile), + A2 = lists:keystore(vsn, 1, A1, {vsn, Vsn}), %% Build the final spec as a string - Spec = io_lib:format("~p.\n", [{application, AppName, A1}]), + Spec = io_lib:format("~p.\n", [{application, AppName, A2}]), %% Setup file .app filename and write new contents AppFile = rebar_app_utils:app_src_to_app(AppSrcFile), ok = file:write_file(AppFile, Spec), - %% Make certain that the ebin/ directory is available on the code path + %% Make certain that the ebin/ directory is available + %% on the code path true = code:add_path(filename:absname(filename:dirname(AppFile))), AppFile; {error, Reason} -> - ?ABORT("Failed to read ~s for preprocessing: ~p\n", [AppSrcFile, Reason]) + ?ABORT("Failed to read ~s for preprocessing: ~p\n", + [AppSrcFile, Reason]) + end. + +load_app_vars(Config) -> + case rebar_config:get_local(Config, app_vars_file, undefined) of + undefined -> + ?INFO("No app_vars_file defined.\n", []), + []; + Filename -> + ?INFO("Loading app vars from ~p\n", [Filename]), + {ok, Vars} = file:consult(Filename), + Vars end. +apply_app_vars([], AppData) -> + AppData; +apply_app_vars([{Key, Value} | Rest], AppData) -> + AppData2 = lists:keystore(Key, 1, AppData, {Key, Value}), + apply_app_vars(Rest, AppData2). validate_name(AppName, File) -> - %% Convert the .app file name to an atom -- check it against the identifier within the file + %% Convert the .app file name to an atom -- check it against the + %% identifier within the file ExpApp = list_to_atom(filename:basename(File, ".app")), case ExpApp == AppName of true -> ok; false -> - ?ERROR("Invalid ~s: name of application (~p) must match filename.\n", - [File, AppName]), + ?ERROR("Invalid ~s: name of application (~p) " + "must match filename.\n", [File, AppName]), ?FAIL end. validate_modules(AppName, undefined) -> - ?ERROR("Missing modules declaration in~p.app:\n", [AppName]), - ?FAIL; + ?ERROR("Missing modules declaration in~p.app:\n", [AppName]), + ?FAIL; validate_modules(AppName, Mods) -> - %% Construct two sets -- one for the actual .beam files in ebin/ and one for the modules + %% Construct two sets -- one for the actual .beam files in ebin/ + %% and one for the modules %% listed in the .app file EbinSet = ordsets:from_list(ebin_modules()), ModSet = ordsets:from_list(Mods), @@ -137,9 +165,10 @@ validate_modules(AppName, Mods) -> [] -> ok; MissingBeams -> - Msg1 = lists:flatten([io_lib:format("\t* ~p\n", [M]) || M <- MissingBeams]), - ?ERROR("One or more modules listed in ~p.app are not present in ebin/*.beam:\n~s", - [AppName, Msg1]), + Msg1 = lists:flatten([io_lib:format("\t* ~p\n", [M]) || + M <- MissingBeams]), + ?ERROR("One or more modules listed in ~p.app are not " + "present in ebin/*.beam:\n~s", [AppName, Msg1]), ?FAIL end, @@ -148,11 +177,13 @@ validate_modules(AppName, Mods) -> [] -> ok; MissingMods -> - Msg2 = lists:flatten([io_lib:format("\t* ~p\n", [M]) || M <- MissingMods]), - ?ERROR("One or more .beam files exist that are not listed in ~p.app:\n~s", - [AppName, Msg2]), + Msg2 = lists:flatten([io_lib:format("\t* ~p\n", [M]) || + M <- MissingMods]), + ?ERROR("One or more .beam files exist that are not " + "listed in ~p.app:\n~s", [AppName, Msg2]), ?FAIL end. ebin_modules() -> - lists:sort([rebar_utils:beam_to_mod("ebin", N) || N <- rebar_utils:beams("ebin")]). + lists:sort([rebar_utils:beam_to_mod("ebin", N) || + N <- rebar_utils:beams("ebin")]). diff --git a/src/rebar_port_compiler.erl b/src/rebar_port_compiler.erl index 732965c..b230af1 100644 --- a/src/rebar_port_compiler.erl +++ b/src/rebar_port_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -37,13 +37,20 @@ %% Supported configuration variables: %% -%% * port_sources - Erlang list of files and/or wildcard strings to be compiled +%% * port_sources - Erlang list of files and/or wildcard strings to be +%% compiled. Platform specific sources can be specified +%% by enclosing a string in a tuple of the form +%% {Regex, String} wherein Regex is a regular expression +%% that is checked against the system architecture. %% -%% * so_specs - Erlang list of tuples of the form {"priv/so_name.so", ["c_src/object_file_name.o"]} useful for -%% building multiple *.so files. +%% * so_specs - Erlang list of tuples of the form +%% {"priv/so_name.so", ["c_src/object_file_name.o"]} +%% useful for building multiple *.so files. %% -%% * port_envs - Erlang list of key/value pairs which will control the environment when -%% running the compiler and linker. By default, the following variables +%% * port_envs - Erlang list of key/value pairs which will control +%% the environment when running the compiler and linker. +%% +%% By default, the following variables %% are defined: %% CC - C compiler %% CXX - C++ compiler @@ -55,70 +62,92 @@ %% DRV_CFLAGS - flags that will be used for compiling the driver %% DRV_LDFLAGS - flags that will be used for linking the driver %% -%% Note that if you wish to extend (vs. replace) these variables, you MUST -%% include a shell-style reference in your definition. E.g. to extend CFLAGS, -%% do something like: +%% Note that if you wish to extend (vs. replace) these variables, +%% you MUST include a shell-style reference in your definition. +%% e.g. to extend CFLAGS, do something like: %% %% {port_envs, [{"CFLAGS", "$CFLAGS -MyOtherOptions"}]} %% -%% It is also possible to specify platform specific options by specifying a triplet -%% where the first string is a regex that is checked against erlang's system architecture -%% string. E.g. to specify a CFLAG that only applies to x86_64 on linux do: +%% It is also possible to specify platform specific options +%% by specifying a tripletwhere the first string is a regex +%% that is checked against erlang's system architecture string. +%% e.g. to specify a CFLAG that only applies to x86_64 on linux +%% do: %% -%% {port_envs, [{"x86_64.*-linux", "CFLAGS", "$CFLAGS -X86Options"}]} +%% {port_envs, [{"x86_64.*-linux", "CFLAGS", +%% "$CFLAGS -X86Options"}]} %% -%% * port_pre_script - Tuple which specifies a pre-compilation script to run, and a filename that -%% exists as a result of the script running. +%% * port_pre_script - Tuple which specifies a pre-compilation script to run, +%% and a filename that exists as a result of the script +%% running. %% -%% * port_cleanup_script - String that specifies a script to run during cleanup. Use this to remove -%% files/directories created by port_pre_script. +%% * port_cleanup_script - String that specifies a script to run during cleanup. +%% Use this to remove files/directories created by +%% port_pre_script. %% compile(Config, AppFile) -> %% Compose list of sources from config file -- defaults to c_src/*.c - Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []), + Sources = expand_sources(rebar_config:get_list(Config, port_sources, + ["c_src/*.c"]), []), case Sources of [] -> ok; _ -> - %% Extract environment values from the config (if specified) and merge with the - %% default for this operating system. This enables max flexibility for users. + %% Extract environment values from the config (if specified) and + %% merge with the default for this operating system. This enables + %% max flexibility for users. DefaultEnvs = filter_envs(default_env(), []), - OverrideEnvs = filter_envs(rebar_config:get_list(Config, port_envs, []), []), - Env = expand_vars_loop(merge_each_var(DefaultEnvs ++ OverrideEnvs ++ os_env(), [])), + PortEnvs = rebar_config:get_list(Config, port_envs, []), + OverrideEnvs = filter_envs(PortEnvs, []), + RawEnv = DefaultEnvs ++ OverrideEnvs ++ os_env(), + Env = expand_vars_loop(merge_each_var(RawEnv, [])), - %% One or more files are available for building. Run the pre-compile hook, if - %% necessary. - run_precompile_hook(Config, Env), + %% One or more files are available for building. + %% Run the pre-compile hook, if necessary. + ok = run_precompile_hook(Config, Env), %% Compile each of the sources - {NewBins, ExistingBins} = compile_each(Sources, Config, Env, [], []), + {NewBins, ExistingBins} = compile_each(Sources, Config, Env, + [], []), %% Construct the driver name and make sure priv/ exists SoSpecs = so_specs(Config, AppFile, NewBins ++ ExistingBins), ?INFO("Using specs ~p\n", [SoSpecs]), - lists:foreach(fun({SoName,_}) -> ok = filelib:ensure_dir(SoName) end, SoSpecs), - - %% Only relink if necessary, given the SoName and list of new binaries - lists:foreach(fun({SoName,Bins}) -> - case needs_link(SoName, sets:to_list(sets:intersection([sets:from_list(Bins),sets:from_list(NewBins)]))) of - true -> - rebar_utils:sh_failfast(?FMT("$CC ~s $LDFLAGS $DRV_LDFLAGS -o ~s", - [string:join(Bins, " "), SoName]), Env); - false -> - ?INFO("Skipping relink of ~s\n", [SoName]), - ok - end + lists:foreach(fun({SoName,_}) -> + ok = filelib:ensure_dir(SoName) + end, SoSpecs), + + %% Only relink if necessary, given the SoName + %% and list of new binaries + lists:foreach( + fun({SoName,Bins}) -> + AllBins = [sets:from_list(Bins), sets:from_list(NewBins)], + Intersection = sets:intersection(AllBins), + case needs_link(SoName, sets:to_list(Intersection)) of + true -> + rebar_utils:sh( + ?FMT("$CC ~s $LDFLAGS $DRV_LDFLAGS -o ~s", + [string:join(Bins, " "), SoName]), + [{env, Env}]); + false -> + ?INFO("Skipping relink of ~s\n", [SoName]), + ok + end end, SoSpecs) end. clean(Config, AppFile) -> %% Build a list of sources so as to derive all the bins we generated - Sources = expand_sources(rebar_config:get_list(Config, port_sources, ["c_src/*.c"]), []), + Sources = expand_sources(rebar_config:get_list(Config, port_sources, + ["c_src/*.c"]), []), rebar_file_utils:delete_each([source_to_bin(S) || S <- Sources]), %% Delete the .so file - rebar_file_utils:delete_each(lists:map(fun({SoName,_}) -> SoName end, so_specs(Config, AppFile, expand_objects(Sources)))), + ExtractSoName = fun({SoName, _}) -> SoName end, + rebar_file_utils:delete_each([ExtractSoName(S) + || S <- so_specs(Config, AppFile, + expand_objects(Sources))]), %% Run the cleanup script, if it exists run_cleanup_hook(Config). @@ -132,10 +161,18 @@ clean(Config, AppFile) -> expand_sources([], Acc) -> Acc; +expand_sources([{ArchRegex, Spec} | Rest], Acc) -> + case rebar_utils:is_arch(ArchRegex) of + true -> + Acc2 = filelib:wildcard(Spec) ++ Acc, + expand_sources(Rest, Acc2); + false -> + expand_sources(Rest, Acc) + end; expand_sources([Spec | Rest], Acc) -> Acc2 = filelib:wildcard(Spec) ++ Acc, expand_sources(Rest, Acc2). - + expand_objects(Sources) -> [filename:join([filename:dirname(F), filename:basename(F) ++ ".o"]) || F <- Sources]. @@ -148,9 +185,11 @@ run_precompile_hook(Config, Env) -> case filelib:is_regular(BypassFileName) of false -> ?CONSOLE("Running ~s\n", [Script]), - rebar_utils:sh_failfast(Script, Env); + {ok, _} = rebar_utils:sh(Script, [{env, Env}]), + ok; true -> - ?INFO("~s exists; not running ~s\n", [BypassFileName, Script]) + ?INFO("~s exists; not running ~s\n", + [BypassFileName, Script]) end end. @@ -160,7 +199,8 @@ run_cleanup_hook(Config) -> ok; Script -> ?CONSOLE("Running ~s\n", [Script]), - rebar_utils:sh_failfast(Script, []) + {ok, _} = rebar_utils:sh(Script, []), + ok end. @@ -174,11 +214,12 @@ compile_each([Source | Rest], Config, Env, NewBins, ExistingBins) -> ?CONSOLE("Compiling ~s\n", [Source]), case compiler(Ext) of "$CC" -> - rebar_utils:sh_failfast(?FMT("$CC -c $CFLAGS $DRV_CFLAGS ~s -o ~s", - [Source, Bin]), Env); + rebar_utils:sh(?FMT("$CC -c $CFLAGS $DRV_CFLAGS ~s -o ~s", + [Source, Bin]), [{env, Env}]); "$CXX" -> - rebar_utils:sh_failfast(?FMT("$CXX -c $CXXFLAGS $DRV_CFLAGS ~s -o ~s", - [Source, Bin]), Env) + rebar_utils:sh( + ?FMT("$CXX -c $CXXFLAGS $DRV_CFLAGS ~s -o ~s", + [Source, Bin]), [{env, Env}]) end, compile_each(Rest, Config, Env, [Bin | NewBins], ExistingBins); @@ -187,10 +228,9 @@ compile_each([Source | Rest], Config, Env, NewBins, ExistingBins) -> compile_each(Rest, Config, Env, NewBins, [Bin | ExistingBins]) end. - - needs_compile(Source, Bin) -> - %% TODO: Generate depends using gcc -MM so we can also check for include changes + %% TODO: Generate depends using gcc -MM so we can also + %% check for include changes filelib:last_modified(Bin) < filelib:last_modified(Source). needs_link(SoName, []) -> @@ -230,8 +270,8 @@ merge_each_var([], Vars) -> merge_each_var([{Key, Value} | Rest], Vars) -> case orddict:find(Key, Vars) of error -> - %% Nothing yet defined for this key/value. Expand any self-references - %% as blank. + %% Nothing yet defined for this key/value. + %% Expand any self-references as blank. Evalue = expand_env_variable(Value, Key, ""); {ok, Value0} -> %% Use previous definition in expansion @@ -264,16 +304,17 @@ expand_vars_loop(Vars0, Count) -> %% Expand all OTHER references to a given K/V pair %% expand_vars(Key, Value, Vars) -> - lists:foldl(fun({AKey, AValue}, Acc) -> - case AKey of - Key -> - NewValue = AValue; - _ -> - NewValue = expand_env_variable(AValue, Key, Value) - end, - [{AKey, NewValue} | Acc] - end, - [], Vars). + lists:foldl( + fun({AKey, AValue}, Acc) -> + case AKey of + Key -> + NewValue = AValue; + _ -> + NewValue = expand_env_variable(AValue, Key, Value) + end, + [{AKey, NewValue} | Acc] + end, + [], Vars). %% @@ -306,7 +347,8 @@ erts_dir() -> lists:concat([code:root_dir(), "/erts-", erlang:system_info(version)]). os_env() -> - Os = [list_to_tuple(re:split(S, "=", [{return, list}, {parts, 2}])) || S <- os:getenv()], + Os = [list_to_tuple(re:split(S, "=", [{return, list}, {parts, 2}])) || + S <- os:getenv()], lists:keydelete([],1,Os). %% Remove Windows current disk and path default_env() -> @@ -320,7 +362,8 @@ default_env() -> " -lerl_interface -lei"])}, {"DRV_CFLAGS", "-g -Wall -fPIC $ERL_CFLAGS"}, {"DRV_LDFLAGS", "-shared $ERL_LDFLAGS"}, - {"darwin", "DRV_LDFLAGS", "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, + {"darwin", "DRV_LDFLAGS", + "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, {"ERLANG_ARCH", integer_to_list(8 * erlang:system_info(wordsize))}, {"ERLANG_TARGET", rebar_utils:get_arch()}, @@ -364,18 +407,20 @@ switch_so_to_dll(Orig = {Name, Spec}) -> make_so_specs(Config, AppFile, Bins) -> case rebar_config:get(Config, so_specs, undefined) of undefined -> - %% New form of so_specs is not provided. See if the old form of {so_name} is available - %% instead + %% New form of so_specs is not provided. See if the old form + %% of {so_name} is available instead + Dir = "priv", SoName = case rebar_config:get(Config, so_name, undefined) of undefined -> - %% Ok, neither old nor new form is available. Use the app name and - %% generate a sensible default. + %% Ok, neither old nor new form is available. Use + %% the app name and generate a sensible default. AppName = rebar_app_utils:app_name(AppFile), - ?FMT("priv/~s", [lists:concat([AppName, "_drv.so"])]); + filename:join(Dir, + lists:concat([AppName, "_drv.so"])); AName -> %% Old form is available -- use it - ?FMT("priv/~s", [AName]) + filename:join(Dir, AName) end, [{SoName, Bins}]; diff --git a/src/rebar_post_script.erl b/src/rebar_post_script.erl index 04daec7..c7f2d01 100644 --- a/src/rebar_post_script.erl +++ b/src/rebar_post_script.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -51,5 +51,6 @@ execute_post_script(Config, Key) -> undefined -> ok; Script -> - rebar_utils:sh(Script, []) + {ok, _} = rebar_utils:sh(Script, []), + ok end. diff --git a/src/rebar_pre_script.erl b/src/rebar_pre_script.erl index d2d7205..9097807 100644 --- a/src/rebar_pre_script.erl +++ b/src/rebar_pre_script.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -51,5 +51,6 @@ execute_pre_script(Config, Key) -> undefined -> ok; Script -> - rebar_utils:sh(Script, []) + {ok, _} = rebar_utils:sh(Script, []), + ok end. diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl index 122440c..421b258 100644 --- a/src/rebar_protobuffs_compiler.erl +++ b/src/rebar_protobuffs_compiler.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -40,8 +40,8 @@ compile(_Config, _AppFile) -> [] -> ok; FoundFiles -> - %% Check for protobuffs library -- if it's not present, fail since we have - %% .proto files that need building + %% Check for protobuffs library -- if it's not present, fail + %% since we have.proto files that need building case protobuffs_is_present() of true -> %% Build a list of output files - { Proto, Beam, Hrl } @@ -51,7 +51,8 @@ compile(_Config, _AppFile) -> %% Compile each proto file compile_each(Targets); false -> - ?ERROR("Protobuffs library not present in code path!\n", []), + ?ERROR("Protobuffs library not present in code path!\n", + []), ?FAIL end end. @@ -60,7 +61,9 @@ compile(_Config, _AppFile) -> clean(_Config, _AppFile) -> %% Get a list of generated .beam and .hrl files and then delete them Protos = filelib:wildcard("src/*.proto"), - Targets = [fq_beam_file(F) || F <- Protos] ++ [fq_hrl_file(F) || F <- Protos], + BeamFiles = [fq_beam_file(F) || F <- Protos], + HrlFiles = [fq_hrl_file(F) || F <- Protos], + Targets = BeamFiles ++ HrlFiles, case Targets of [] -> ok; @@ -100,15 +103,18 @@ compile_each([{Proto, Beam, Hrl} | Rest]) -> ?CONSOLE("Compiling ~s\n", [Proto]), case protobuffs_compile:scan_file(Proto) of ok -> - %% Compilation worked, but we need to move the .beam and .hrl file - %% into the ebin/ and include/ directories respectively - %% TODO: Protobuffs really needs to be better about this...sigh. - [] = os:cmd(?FMT("mv ~s ebin", [Beam])), + %% Compilation worked, but we need to move the + %% beam and .hrl file into the ebin/ and include/ + %% directories respectively + %% TODO: Protobuffs really needs to be better about this + ok = filelib:ensure_dir(filename:join("ebin","dummy")), + ok = rebar_file_utils:mv(Beam, "ebin"), ok = filelib:ensure_dir(filename:join("include", Hrl)), - [] = os:cmd(?FMT("mv ~s include", [Hrl])), + ok = rebar_file_utils:mv(Hrl, "include"), ok; Other -> - ?ERROR("Protobuff compile of ~s failed: ~p\n", [Proto, Other]), + ?ERROR("Protobuff compile of ~s failed: ~p\n", + [Proto, Other]), ?FAIL end; false -> diff --git a/src/rebar_rel_utils.erl b/src/rebar_rel_utils.erl index b955a53..9729e20 100644 --- a/src/rebar_rel_utils.erl +++ b/src/rebar_rel_utils.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -26,16 +26,62 @@ %% ------------------------------------------------------------------- -module(rebar_rel_utils). --export([is_rel_dir/0, is_rel_dir/1]). +-export([is_rel_dir/0, + is_rel_dir/1, + get_reltool_release_info/1, + get_rel_release_info/1, + get_rel_release_info/2, + get_previous_release_path/0]). + +-include("rebar.hrl"). is_rel_dir() -> is_rel_dir(rebar_utils:get_cwd()). is_rel_dir(Dir) -> Fname = filename:join([Dir, "reltool.config"]), - case filelib:is_file(Fname) of + case filelib:is_regular(Fname) of true -> {true, Fname}; false -> false end. + +%% Get release name and version from a reltool.config +get_reltool_release_info(ReltoolFile) -> + %% expect sys to be the first proplist in reltool.config + case file:consult(ReltoolFile) of + {ok, [{sys, Config}| _]} -> + %% expect the first rel in the proplist to be the one you want + {rel, Name, Ver, _} = proplists:lookup(rel, Config), + {Name, Ver}; + _ -> + ?ABORT("Failed to parse ~s~n", [ReltoolFile]) + end. + +%% Get release name and version from a rel file +get_rel_release_info(RelFile) -> + case file:consult(RelFile) of + {ok, [{release, {Name, Ver}, _, _}]} -> + {Name, Ver}; + _ -> + ?ABORT("Failed to parse ~s~n", [RelFile]) + end. + +%% Get release name and version from a name and a path +get_rel_release_info(Name, Path) -> + [RelFile] = filelib:wildcard(filename:join([Path, "releases", "*", + Name ++ ".rel"])), + [BinDir|_] = re:replace(RelFile, Name ++ "\\.rel", ""), + get_rel_release_info(filename:join([binary_to_list(BinDir), + Name ++ ".rel"])). + +%% Get the previous release path from a global variable +get_previous_release_path() -> + case rebar_config:get_global(previous_release, false) of + false -> + ?ABORT("previous_release=PATH is required to " + "create upgrade package~n", []); + OldVerPath -> + OldVerPath + end. diff --git a/src/rebar_reltool.erl b/src/rebar_reltool.erl index b8e1095..5cbd587 100644 --- a/src/rebar_reltool.erl +++ b/src/rebar_reltool.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -78,13 +78,14 @@ clean(_Config, ReltoolFile) -> check_vsn() -> case code:lib_dir(reltool) of {error, bad_name} -> - ?ABORT("Reltool support requires the reltool application to be installed!", []); + ?ABORT("Reltool support requires the reltool application " + "to be installed!", []); Path -> ReltoolVsn = filename:basename(Path), case ReltoolVsn < "reltool-0.5.2" of true -> - ?ABORT("Reltool support requires at least reltool-0.5.2; this VM is using ~s\n", - [ReltoolVsn]); + ?ABORT("Reltool support requires at least reltool-0.5.2; " + "this VM is using ~s\n", [ReltoolVsn]); false -> ok end @@ -98,12 +99,13 @@ load_config(ReltoolFile) -> {ok, Terms} -> Terms; Other -> - ?ABORT("Failed to load expected config from ~s: ~p\n", [ReltoolFile, Other]) + ?ABORT("Failed to load expected config from ~s: ~p\n", + [ReltoolFile, Other]) end. %% -%% Look for the {sys, [...]} tuple in the reltool.config file. Without this present, we -%% can't run reltool. +%% Look for the {sys, [...]} tuple in the reltool.config file. +%% Without this present, we can't run reltool. %% sys_tuple(ReltoolConfig) -> case lists:keyfind(sys, 1, ReltoolConfig) of @@ -160,15 +162,17 @@ validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) -> false -> ok; {rel, _Name, _Vsn, Apps} -> - %% Identify all the apps that do NOT exist, based on what's available - %% from the reltool server - Missing = lists:sort([App || App <- Apps, - app_exists(App, ReltoolServer) == false]), + %% Identify all the apps that do NOT exist, based on + %% what's available from the reltool server + Missing = lists:sort( + [App || App <- Apps, + app_exists(App, ReltoolServer) == false]), case Missing of [] -> ok; _ -> - ?ABORT("Apps in {rel, ...} section not found by reltool: ~p\n", [Missing]) + ?ABORT("Apps in {rel, ...} section not found by " + "reltool: ~p\n", [Missing]) end; Rel -> %% Invalid release format! @@ -201,10 +205,12 @@ run_reltool(Server, _Config, ReltoolConfig) -> ok -> ok; {error, Reason} -> - ?ABORT("Failed to generate target from spec: ~p\n", [Reason]) + ?ABORT("Failed to generate target from spec: ~p\n", + [Reason]) end, - %% Initialize overlay vars with some basics (that can get overwritten) + %% Initialize overlay vars with some basics + %% (that can get overwritten) OverlayVars0 = [{erts_vsn, "erts-" ++ erlang:system_info(version)}], %% Load up any variables specified by overlay_vars @@ -216,19 +222,22 @@ run_reltool(Server, _Config, ReltoolConfig) -> {ok, Terms} -> dict:from_list(OverlayVars0 ++ Terms); {error, Reason2} -> - ?ABORT("Unable to load overlay_vars from ~s: ~p\n", + ?ABORT("Unable to load overlay_vars " + "from ~s: ~p\n", [File, Reason2]) end end, %% Finally, overlay the files specified by the overlay section - case lists:keysearch(overlay, 1, ReltoolConfig) of - {value, {overlay, Overlay}} when is_list(Overlay) -> - execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(), TargetDir); - {value, _} -> - ?ABORT("{overlay, [...]} entry in reltool.config must be a list.\n", []); + case lists:keyfind(overlay, 1, ReltoolConfig) of + {overlay, Overlay} when is_list(Overlay) -> + execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(), + TargetDir); false -> - ?INFO("No {overlay, [...]} found in reltool.config.\n", []) + ?INFO("No {overlay, [...]} found in reltool.config.\n", []); + _ -> + ?ABORT("{overlay, [...]} entry in reltool.config " + "must be a list.\n", []) end; {error, Reason} -> @@ -247,7 +256,8 @@ mk_target_dir(TargetDir) -> rebar_file_utils:rm_rf(TargetDir), ok = file:make_dir(TargetDir); _ -> - ?ERROR("Release target directory ~p already exists!\n", [TargetDir]), + ?ERROR("Release target directory ~p already exists!\n", + [TargetDir]), ?FAIL end end. @@ -308,12 +318,14 @@ execute_overlay([{create, Out, Contents} | Rest], Vars, BaseDir, TargetDir) -> end; execute_overlay([{replace, Out, Regex, Replacement} | Rest], Vars, BaseDir, TargetDir) -> - execute_overlay([{replace, Out, Regex, Replacement, []} | Rest], Vars, BaseDir, TargetDir); + execute_overlay([{replace, Out, Regex, Replacement, []} | Rest], + Vars, BaseDir, TargetDir); execute_overlay([{replace, Out, Regex, Replacement, Opts} | Rest], Vars, BaseDir, TargetDir) -> Filename = render(filename:join(TargetDir, Out), Vars), {ok, OrigData} = file:read_file(Filename), - Data = re:replace(OrigData, Regex, Replacement, [global, {return, binary}] ++ Opts), + Data = re:replace(OrigData, Regex, Replacement, + [global, {return, binary}] ++ Opts), case file:write_file(Filename, Data) of ok -> ?DEBUG("Edited ~s: s/~s/~s/\n", [Filename, Regex, Replacement]), diff --git a/src/rebar_require_vsn.erl b/src/rebar_require_vsn.erl index 96fc088..a9991e9 100644 --- a/src/rebar_require_vsn.erl +++ b/src/rebar_require_vsn.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -45,7 +45,8 @@ eunit(Config, _) -> check_versions(Config) -> ErtsRegex = rebar_config:get(Config, require_erts_vsn, ".*"), - case re:run(erlang:system_info(version), ErtsRegex, [{capture, none}]) of + ReOpts = [{capture, none}], + case re:run(erlang:system_info(version), ErtsRegex, ReOpts) of match -> ?DEBUG("Matched required ERTS version: ~s -> ~s\n", [erlang:system_info(version), ErtsRegex]); @@ -55,7 +56,7 @@ check_versions(Config) -> end, OtpRegex = rebar_config:get(Config, require_otp_vsn, ".*"), - case re:run(erlang:system_info(otp_release), OtpRegex, [{capture, none}]) of + case re:run(erlang:system_info(otp_release), OtpRegex, ReOpts) of match -> ?DEBUG("Matched required OTP release: ~s -> ~s\n", [erlang:system_info(otp_release), OtpRegex]); @@ -63,6 +64,3 @@ check_versions(Config) -> ?ABORT("OTP release ~s does not match required regex ~s\n", [erlang:system_info(otp_release), OtpRegex]) end. - - - diff --git a/src/rebar_subdirs.erl b/src/rebar_subdirs.erl index 7e39ffa..b107978 100644 --- a/src/rebar_subdirs.erl +++ b/src/rebar_subdirs.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -37,6 +37,6 @@ preprocess(Config, _) -> %% Get the list of subdirs specified in the config (if any). Cwd = rebar_utils:get_cwd(), - Subdirs = [filename:join(Cwd, Dir) || Dir <- rebar_config:get_local(Config, sub_dirs, [])], + Subdirs = [filename:join(Cwd, Dir) || + Dir <- rebar_config:get_local(Config, sub_dirs, [])], {ok, Subdirs}. - diff --git a/src/rebar_templater.erl b/src/rebar_templater.erl index 4aa5ed6..487a578 100644 --- a/src/rebar_templater.erl +++ b/src/rebar_templater.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -75,11 +75,13 @@ create(_Config, _) -> AvailTemplates = find_disk_templates() ++ find_escript_templates(), ?DEBUG("Available templates: ~p\n", [AvailTemplates]), + TemplateId = template_id(), + %% Using the specified template id, find the matching template file/type. %% Note that if you define the same template in both ~/.rebar/templates %% that is also present in the escript, the one on the file system will %% be preferred. - {Type, Template} = select_template(AvailTemplates, template_id()), + {Type, Template} = select_template(AvailTemplates, TemplateId), %% Load the template definition as is and get the list of variables the %% template requires. @@ -92,25 +94,32 @@ create(_Config, _) -> ?ABORT("Failed while processing variables from template ~p." "Variable definitions must follow form of " "[{atom(), term()}]. Failed at: ~p\n", - [template_id(), Entry]); + [TemplateId, Entry]); Context0 -> ok end; false -> - ?WARN("No variables section found in template ~p; using empty context.\n", - [template_id()]), + ?WARN("No variables section found in template ~p; " + "using empty context.\n", [TemplateId]), Context0 = dict:new() end, - %% For each variable, see if it's defined in global vars -- if it is, prefer that - %% value over the defaults - Context = update_vars(dict:fetch_keys(Context0), Context0), - ?DEBUG("Template ~p context: ~p\n", [template_id(), dict:to_list(Context)]), + %% For each variable, see if it's defined in global vars -- if it is, + %% prefer that value over the defaults + Context1 = update_vars(dict:fetch_keys(Context0), Context0), + ?DEBUG("Template ~p context: ~p\n", [TemplateId, dict:to_list(Context1)]), + + %% Handle variables that possibly include other variables in their + %% definition + Context = resolve_recursive_vars(dict:to_list(Context1), Context1), - %% Now, use our context to process the template definition -- this permits us to - %% use variables within the definition for filenames. + ?DEBUG("Resolved Template ~p context: ~p\n", + [TemplateId, dict:to_list(Context1)]), + + %% Now, use our context to process the template definition -- this + %% permits us to use variables within the definition for filenames. FinalTemplate = consult(render(load_file(Type, Template), Context)), - ?DEBUG("Final template def ~p: ~p\n", [template_id(), FinalTemplate]), + ?DEBUG("Final template def ~p: ~p\n", [TemplateId, FinalTemplate]), %% Execute the instructions in the finalized template Force = rebar_config:get_global(force, "0"), @@ -128,10 +137,10 @@ create(_Config, _) -> %% cache_escript_files() -> {ok, Files} = rebar_utils:escript_foldl( - fun(Name, _, GetBin, Acc) -> - [{Name, GetBin()} | Acc] - end, - [], rebar_config:get_global(escript, undefined)), + fun(Name, _, GetBin, Acc) -> + [{Name, GetBin()} | Acc] + end, + [], rebar_config:get_global(escript, undefined)), erlang:put(escript_files, Files). @@ -150,7 +159,8 @@ find_escript_templates() -> find_disk_templates() -> OtherTemplates = find_other_templates(), HomeFiles = rebar_utils:find_files(filename:join(os:getenv("HOME"), - ".rebar/templates"), ?TEMPLATE_RE), + ".rebar/templates"), + ?TEMPLATE_RE), LocalFiles = rebar_utils:find_files(".", ?TEMPLATE_RE), [{file, F} || F <- OtherTemplates ++ HomeFiles ++ LocalFiles]. @@ -206,6 +216,18 @@ update_vars([Key | Rest], Dict) -> %% +%% Given a list of key value pairs, for each string value attempt to +%% render it using Dict as the context. Storing the result in Dict as Key. +%% +resolve_recursive_vars([], Dict) -> + Dict; +resolve_recursive_vars([{Key, Value0} | Rest], Dict) when is_list(Value0) -> + Value = render(list_to_binary(Value0), Dict), + resolve_recursive_vars(Rest, dict:store(Key, Value, Dict)); +resolve_recursive_vars([_Pair | Rest], Dict) -> + resolve_recursive_vars(Rest, Dict). + +%% %% Given a string or binary, parse it into a list of terms, ala file:consult/0 %% consult(Str) when is_list(Str) -> @@ -240,18 +262,18 @@ render(Bin, Context) -> write_file(Output, Data, Force) -> %% determine if the target file already exists - FileExists = filelib:is_file(Output), + FileExists = filelib:is_regular(Output), %% perform the function if we're allowed, %% otherwise just process the next template - if - Force =:= "1"; FileExists =:= false -> + case Force =:= "1" orelse FileExists =:= false of + true -> ok = filelib:ensure_dir(Output), - if - {Force, FileExists} =:= {"1", true} -> + case {Force, FileExists} of + {"1", true} -> ?CONSOLE("Writing ~s (forcibly overwriting)~n", [Output]); - true -> + _ -> ?CONSOLE("Writing ~s~n", [Output]) end, case file:write_file(Output, Data) of @@ -261,7 +283,7 @@ write_file(Output, Data, Force) -> ?ABORT("Failed to write output file ~p: ~p\n", [Output, Reason]) end; - true -> + false -> {error, exists} end. @@ -269,18 +291,24 @@ write_file(Output, Data, Force) -> %% %% Execute each instruction in a template definition file. %% -execute_template([], _TemplateType, _TemplateName, _Context, _Force, ExistingFiles) -> +execute_template([], _TemplateType, _TemplateName, _Context, + _Force, ExistingFiles) -> case ExistingFiles of [] -> ok; _ -> - Msg = lists:flatten([io_lib:format("\t* ~p~n", [F]) || F <- lists:reverse(ExistingFiles)]), - Help = "To force overwriting, specify force=1 on the command line.\n", - ?ERROR("One or more files already exist on disk and were not generated:~n~s~s", [Msg , Help]) + Msg = lists:flatten([io_lib:format("\t* ~p~n", [F]) || + F <- lists:reverse(ExistingFiles)]), + Help = + "To force overwriting, specify force=1 on the command line.\n", + ?ERROR("One or more files already exist on disk and " + "were not generated:~n~s~s", [Msg , Help]) end; -execute_template([{template, Input, Output} | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) -> +execute_template([{template, Input, Output} | Rest], TemplateType, + TemplateName, Context, Force, ExistingFiles) -> InputName = filename:join(filename:dirname(TemplateName), Input), - case write_file(Output, render(load_file(TemplateType, InputName), Context), Force) of + case write_file(Output, render(load_file(TemplateType, InputName), Context), + Force) of ok -> execute_template(Rest, TemplateType, TemplateName, Context, Force, ExistingFiles); @@ -288,35 +316,43 @@ execute_template([{template, Input, Output} | Rest], TemplateType, TemplateName, execute_template(Rest, TemplateType, TemplateName, Context, Force, [Output|ExistingFiles]) end; -execute_template([{file, Input, Output} | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) -> +execute_template([{file, Input, Output} | Rest], TemplateType, TemplateName, + Context, Force, ExistingFiles) -> InputName = filename:join(filename:dirname(TemplateName), Input), case write_file(Output, load_file(TemplateType, InputName), Force) of ok -> - execute_template(Rest, TemplateType, TemplateName, Context, - Force, ExistingFiles); + execute_template(Rest, TemplateType, TemplateName, + Context, Force, ExistingFiles); {error, exists} -> - execute_template(Rest, TemplateType, TemplateName, Context, - Force, [Output|ExistingFiles]) + execute_template(Rest, TemplateType, TemplateName, + Context, Force, [Output|ExistingFiles]) end; -execute_template([{dir, Name} | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) -> +execute_template([{dir, Name} | Rest], TemplateType, TemplateName, Context, + Force, ExistingFiles) -> case filelib:ensure_dir(filename:join(Name, "dummy")) of ok -> - execute_template(Rest, TemplateType, TemplateName, Context, Force, ExistingFiles); + execute_template(Rest, TemplateType, TemplateName, + Context, Force, ExistingFiles); {error, Reason} -> - ?ABORT("Failed while processing template instruction {dir, ~s}: ~p\n", - [Name, Reason]) + ?ABORT("Failed while processing template instruction " + "{dir, ~s}: ~p\n", [Name, Reason]) end; -execute_template([{chmod, Mod, File} | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) when is_integer(Mod) -> +execute_template([{chmod, Mod, File} | Rest], TemplateType, TemplateName, + Context, Force, ExistingFiles) when is_integer(Mod) -> case file:change_mode(File, Mod) of ok -> - execute_template(Rest, TemplateType, TemplateName, Context, Force, ExistingFiles); + execute_template(Rest, TemplateType, TemplateName, + Context, Force, ExistingFiles); {error, Reason} -> - ?ABORT("Failed while processing template instruction {cmod, ~b, ~s}: ~p~n", - [Mod, File, Reason]) + ?ABORT("Failed while processing template instruction " + "{cmod, ~b, ~s}: ~p~n", [Mod, File, Reason]) end; -execute_template([{variables, _} | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) -> - execute_template(Rest, TemplateType, TemplateName, Context, Force, ExistingFiles); -execute_template([Other | Rest], TemplateType, TemplateName, Context, Force, ExistingFiles) -> +execute_template([{variables, _} | Rest], TemplateType, TemplateName, Context, + Force, ExistingFiles) -> + execute_template(Rest, TemplateType, TemplateName, + Context, Force, ExistingFiles); +execute_template([Other | Rest], TemplateType, TemplateName, Context, + Force, ExistingFiles) -> ?WARN("Skipping unknown template instruction: ~p\n", [Other]), - execute_template(Rest, TemplateType, TemplateName, Context, Force, ExistingFiles). - + execute_template(Rest, TemplateType, TemplateName, Context, + Force, ExistingFiles). diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl new file mode 100644 index 0000000..39b2c61 --- /dev/null +++ b/src/rebar_upgrade.erl @@ -0,0 +1,186 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2011 Joe Williams (joe@joetify.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- + +-module(rebar_upgrade). + +-include("rebar.hrl"). +-include_lib("kernel/include/file.hrl"). + +-export(['generate-upgrade'/2]). + +%% ==================================================================== +%% Public API +%% ==================================================================== + +'generate-upgrade'(_Config, ReltoolFile) -> + %% Get the old release path + OldVerPath = rebar_rel_utils:get_previous_release_path(), + + %% Run checks to make sure that building a package is possible + {NewName, NewVer} = run_checks(OldVerPath, ReltoolFile), + NameVer = NewName ++ "_" ++ NewVer, + + %% Save the code path prior to doing anything + OrigPath = code:get_path(), + + %% Prepare the environment for building the package + ok = setup(OldVerPath, NewName, NewVer, NameVer), + + %% Build the package + run_systools(NameVer, NewName), + + %% Boot file changes + {ok, _} = boot_files(NewVer, NewName), + + %% Extract upgrade and tar it back up with changes + make_tar(NameVer), + + %% Clean up files that systools created + ok = cleanup(NameVer, NewName, NewVer), + + %% Restore original path + true = code:set_path(OrigPath), + + ok. + +%% =================================================================== +%% Internal functions +%% ================================================================== + +run_checks(OldVerPath, ReltoolFile) -> + true = rebar_utils:prop_check(filelib:is_dir(OldVerPath), + "Release directory doesn't exist (~p)~n", [OldVerPath]), + + {Name, Ver} = rebar_rel_utils:get_reltool_release_info(ReltoolFile), + + NamePath = filename:join([".", Name]), + true = rebar_utils:prop_check(filelib:is_dir(NamePath), + "Release directory doesn't exist (~p)~n", [NamePath]), + + {NewName, NewVer} = rebar_rel_utils:get_rel_release_info(Name, NamePath), + {OldName, OldVer} = rebar_rel_utils:get_rel_release_info(Name, OldVerPath), + + true = rebar_utils:prop_check(NewName == OldName, + "New and old .rel release names do not match~n", []), + true = rebar_utils:prop_check(Name == NewName, + "Reltool and .rel release names do not match~n", []), + true = rebar_utils:prop_check(NewVer =/= OldVer, + "New and old .rel contain the same version~n", []), + true = rebar_utils:prop_check(Ver == NewVer, + "Reltool and .rel versions do not match~n", []), + + {NewName, NewVer}. + +setup(OldVerPath, NewName, NewVer, NameVer) -> + NewRelPath = filename:join([".", NewName]), + Src = filename:join([NewRelPath, "releases", + NewVer, NewName ++ ".rel"]), + Dst = filename:join([".", NameVer ++ ".rel"]), + {ok, _} = file:copy(Src, Dst), + ok = code:add_pathsa( + lists:append([ + filelib:wildcard(filename:join([OldVerPath, + "releases", "*"])), + filelib:wildcard(filename:join([OldVerPath, + "lib", "*", "ebin"])), + filelib:wildcard(filename:join([NewRelPath, + "lib", "*", "ebin"])), + filelib:wildcard(filename:join([NewRelPath, "*"])) + ])). + +run_systools(NewVer, Name) -> + Opts = [silent], + NameList = [Name], + case systools:make_relup(NewVer, NameList, NameList, Opts) of + {error, _, _Message} -> + ?ABORT("Systools aborted with: ~p~n", [_Message]); + _ -> + ?DEBUG("Relup created~n", []), + case systools:make_script(NewVer, Opts) of + {error, _, _Message1} -> + ?ABORT("Systools aborted with: ~p~n", [_Message1]); + _ -> + ?DEBUG("Script created~n", []), + case systools:make_tar(NewVer, Opts) of + {error, _, _Message2} -> + ?ABORT("Systools aborted with: ~p~n", [_Message2]); + _ -> + ok + end + end + end. + +boot_files(Ver, Name) -> + ok = file:make_dir(filename:join([".", "releases"])), + ok = file:make_dir(filename:join([".", "releases", Ver])), + ok = file:make_symlink( + filename:join(["start.boot"]), + filename:join([".", "releases", Ver, Name ++ ".boot"])), + {ok, _} = file:copy( + filename:join([".", Name, "releases", Ver, "start_clean.boot"]), + filename:join([".", "releases", Ver, "start_clean.boot"])). + +make_tar(NameVer) -> + Filename = NameVer ++ ".tar.gz", + ok = erl_tar:extract(Filename, [compressed]), + ok = file:delete(Filename), + {ok, Tar} = erl_tar:open(Filename, [write, compressed]), + ok = erl_tar:add(Tar, "lib", []), + ok = erl_tar:add(Tar, "releases", []), + ok = erl_tar:close(Tar), + ?CONSOLE("~s upgrade package created~n", [NameVer]). + +cleanup(NameVer, Name, Ver) -> + ?DEBUG("Removing files needed for building the upgrade~n", []), + Files = [ + filename:join([".", "releases", Ver, Name ++ ".boot"]), + filename:join([".", NameVer ++ ".rel"]), + filename:join([".", NameVer ++ ".boot"]), + filename:join([".", NameVer ++ ".script"]), + filename:join([".", "relup"]) + ], + lists:foreach(fun(F) -> ok = file:delete(F) end, Files), + + ok = remove_dir_tree("releases"), + ok = remove_dir_tree("lib"). + +%% taken from http://www.erlang.org/doc/system_principles/create_target.html +remove_dir_tree(Dir) -> + remove_all_files(".", [Dir]). +remove_all_files(Dir, Files) -> + lists:foreach(fun(File) -> + FilePath = filename:join([Dir, File]), + {ok, FileInfo} = file:read_file_info(FilePath), + case FileInfo#file_info.type of + directory -> + {ok, DirFiles} = file:list_dir(FilePath), + remove_all_files(FilePath, DirFiles), + file:del_dir(FilePath); + _ -> + file:delete(FilePath) + end + end, Files). diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 98fdbd7..2822c0f 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -29,9 +29,7 @@ -export([get_cwd/0, is_arch/1, get_arch/0, - get_os/0, - sh/2, sh/3, - sh_failfast/2, + sh/2, find_files/2, now_str/0, ensure_dir/1, @@ -39,7 +37,8 @@ erl_to_mod/1, abort/2, escript_foldl/3, - find_executable/1]). + find_executable/1, + prop_check/3]). -include("rebar.hrl"). @@ -62,47 +61,52 @@ is_arch(ArchRegex) -> get_arch() -> Words = integer_to_list(8 * erlang:system_info(wordsize)), - erlang:system_info(system_architecture) ++ "-" ++ Words. + erlang:system_info(otp_release) ++ "-" + ++ erlang:system_info(system_architecture) ++ "-" ++ Words. -get_os() -> - Arch = erlang:system_info(system_architecture), - case match_first([{"linux", linux}, {"darwin", darwin}], Arch) of - nomatch -> - {unknown, Arch}; - ArchAtom -> - ArchAtom - end. +%% +%% Options = [Option] -- defaults to [use_stdout, abort_on_error] +%% Option = ErrorOption | OutputOption | {cd, string()} | {env, Env} +%% ErrorOption = return_on_error | abort_on_error | {abort_on_error, string()} +%% OutputOption = use_stdout | {use_stdout, bool()} +%% Env = [{string(), Val}] +%% Val = string() | false +%% +sh(Command0, Options0) -> + ?INFO("sh: ~s\n~p\n", [Command0, Options0]), + DefaultOptions = [use_stdout, abort_on_error], + Options = [expand_sh_flag(V) + || V <- proplists:compact(Options0 ++ DefaultOptions)], -sh(Command, Env) -> - sh(Command, Env, get_cwd()). + ErrorHandler = proplists:get_value(error_handler, Options), + OutputHandler = proplists:get_value(output_handler, Options), -sh(Command0, Env, Dir) -> - ?INFO("sh: ~s\n~p\n", [Command0, Env]), - Command = patch_on_windows(Command0, os:type()), - Port = open_port({spawn, Command}, [{cd, Dir}, {env, Env}, exit_status, {line, 16384}, - use_stdio, stderr_to_stdout]), - case sh_loop(Port) of - ok -> - ok; + Command = patch_on_windows(Command0), + PortSettings = proplists:get_all_values(port_settings, Options) ++ + [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], + Port = open_port({spawn, Command}, PortSettings), + + case sh_loop(Port, OutputHandler, []) of + {ok, _Output} = Ok -> + Ok; {error, Rc} -> - ?ABORT("~s failed with error: ~w\n", [Command, Rc]) + ErrorHandler(Command, Rc) end. - %% We need a bash shell to execute on windows %% also the port doesn't seem to close from time to time (mingw) -patch_on_windows(Cmd, {win32,nt}) -> - case find_executable("bash") of - false -> Cmd; - Bash -> - Bash ++ " -c \"" ++ Cmd ++ "; echo _port_cmd_status_ $?\" " - end; -patch_on_windows(Command, _) -> - Command. - -sh_failfast(Command, Env) -> - sh(Command, Env). +patch_on_windows(Cmd) -> + case os:type() of + {win32,nt} -> + case find_executable("bash") of + false -> Cmd; + Bash -> + Bash ++ " -c \"" ++ Cmd ++ "; echo _port_cmd_status_ $?\" " + end; + _ -> + Cmd + end. find_files(Dir, Regex) -> filelib:fold_files(Dir, Regex, true, fun(F, Acc) -> [F | Acc] end, []). @@ -110,7 +114,7 @@ find_files(Dir, Regex) -> now_str() -> {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), lists:flatten(io_lib:format("~4b/~2..0b/~2..0b ~2..0b:~2..0b:~2..0b", - [Year, Month, Day, Hour, Minute, Second])). + [Year, Month, Day, Hour, Minute, Second])). %% TODO: filelib:ensure_dir/1 corrected in R13B04. Remove when we drop %% support for OTP releases older than R13B04. @@ -148,33 +152,61 @@ find_executable(Name) -> "\"" ++ filename:nativename(Path) ++ "\"" end. +%% Helper function for checking values and aborting when needed +prop_check(true, _, _) -> true; +prop_check(false, Msg, Args) -> ?ABORT(Msg, Args). + %% ==================================================================== %% Internal functions %% ==================================================================== -match_first([], _Val) -> - nomatch; -match_first([{Regex, MatchValue} | Rest], Val) -> - case re:run(Val, Regex, [{capture, none}]) of - match -> - MatchValue; - nomatch -> - match_first(Rest, Val) - end. - -sh_loop(Port) -> +expand_sh_flag(return_on_error) -> + {error_handler, + fun(_Command, Rc) -> + {error, Rc} + end}; +expand_sh_flag({abort_on_error, Message}) -> + {error_handler, + fun(_Command, _Rc) -> + ?ABORT(Message, []) + end}; +expand_sh_flag(abort_on_error) -> + {error_handler, + fun log_and_abort/2}; +expand_sh_flag(use_stdout) -> + {output_handler, + fun(Line, Acc) -> + ?CONSOLE("~s", [Line]), + [Acc | Line] + end}; +expand_sh_flag({use_stdout, false}) -> + {output_handler, + fun(Line, Acc) -> + [Acc | Line] + end}; +expand_sh_flag({cd, _CdArg} = Cd) -> + {port_settings, Cd}; +expand_sh_flag({env, _EnvArg} = Env) -> + {port_settings, Env}. + +-spec log_and_abort(string(), integer()) -> no_return(). +log_and_abort(Command, Rc) -> + ?ABORT("~s failed with error: ~w\n", [Command, Rc]). + +sh_loop(Port, Fun, Acc) -> receive {Port, {data, {_, "_port_cmd_status_ " ++ Status}}} -> (catch erlang:port_close(Port)), % sigh () for indentation case list_to_integer(Status) of - 0 -> ok; + 0 -> {ok, lists:flatten(Acc)}; Rc -> {error, Rc} end; - {Port, {data, {_, Line}}} -> - ?CONSOLE("~s\n", [Line]), - sh_loop(Port); + {Port, {data, {eol, Line}}} -> + sh_loop(Port, Fun, Fun(Line ++ "\n", Acc)); + {Port, {data, {noeol, Line}}} -> + sh_loop(Port, Fun, Fun(Line, Acc)); {Port, {exit_status, 0}} -> - ok; + {ok, lists:flatten(Acc)}; {Port, {exit_status, Rc}} -> {error, Rc} end. diff --git a/src/rebar_xref.erl b/src/rebar_xref.erl index 55b4f0e..f46ccfc 100644 --- a/src/rebar_xref.erl +++ b/src/rebar_xref.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -44,8 +44,11 @@ xref(Config, _) -> %% Spin up xref {ok, _} = xref:start(xref), ok = xref:set_library_path(xref, code_path()), - xref:set_default(xref, [{warnings, rebar_config:get(Config, xref_warnings, false)}, + + xref:set_default(xref, [{warnings, + rebar_config:get(Config, xref_warnings, false)}, {verbose, rebar_config:is_verbose()}]), + {ok, _} = xref:add_directory(xref, "ebin"), %% Save the code path prior to doing anything @@ -53,8 +56,9 @@ xref(Config, _) -> true = code:add_path(filename:join(rebar_utils:get_cwd(), "ebin")), %% Get list of xref checks we want to run - XrefChecks = rebar_config:get(Config, xref_checks, [exports_not_used, - undefined_function_calls]), + XrefChecks = rebar_config:get(Config, xref_checks, + [exports_not_used, + undefined_function_calls]), %% Look for exports that are unused by anything case lists:member(exports_not_used, XrefChecks) of @@ -91,12 +95,15 @@ check_exports_not_used(_Config) -> check_undefined_function_calls(_Config) -> {ok, UndefinedCalls0} = xref:analyze(xref, undefined_function_calls), - UndefinedCalls = [{find_mfa_source(Caller), format_fa(Caller), format_mfa(Target)} || - {Caller, Target} <- UndefinedCalls0], - lists:foreach(fun({{Source, Line}, FunStr, Target}) -> - ?CONSOLE("~s:~w: Warning ~s calls undefined function ~s\n", - [Source, Line, FunStr, Target]) - end, UndefinedCalls), + UndefinedCalls = + [{find_mfa_source(Caller), format_fa(Caller), format_mfa(Target)} || + {Caller, Target} <- UndefinedCalls0], + + lists:foreach( + fun({{Source, Line}, FunStr, Target}) -> + ?CONSOLE("~s:~w: Warning ~s calls undefined function ~s\n", + [Source, Line, FunStr, Target]) + end, UndefinedCalls), ok. @@ -111,17 +118,20 @@ filter_away_ignored(UnusedExports) -> %% Functions can be ignored by using %% -ignore_xref([{F, A}, ...]). - %% Setup a filter function that build a list of behaviour callbacks and/or - %% any functions marked to ignore. We then use this list to mask any functions - %% marked as unused exports by xref + %% Setup a filter function that builds a list of behaviour callbacks and/or + %% any functions marked to ignore. We then use this list to mask any + %% functions marked as unused exports by xref F = fun(Mod) -> Attrs = kf(attributes, Mod:module_info()), Ignore = kf(ignore_xref, Attrs), - Callbacks = [B:behaviour_info(callbacks) || B <- kf(behaviour, Attrs)], + Callbacks = + [B:behaviour_info(callbacks) || B <- kf(behaviour, Attrs)], [{Mod, F, A} || {F, A} <- Ignore ++ lists:flatten(Callbacks)] end, - AttrIgnore = lists:flatten(lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))), - [X || X <- UnusedExports, not(lists:member(X, AttrIgnore))]. + AttrIgnore = + lists:flatten( + lists:map(F, lists:usort([M || {M, _, _} <- UnusedExports]))), + [X || X <- UnusedExports, not lists:member(X, AttrIgnore)]. kf(Key, List) -> @@ -136,7 +146,8 @@ display_mfas([], _Message) -> ok; display_mfas([{_Mod, Fun, Args} = MFA | Rest], Message) -> {Source, Line} = find_mfa_source(MFA), - ?CONSOLE("~s:~w: Warning: function ~s/~w ~s\n", [Source, Line, Fun, Args, Message]), + ?CONSOLE("~s:~w: Warning: function ~s/~w ~s\n", + [Source, Line, Fun, Args, Message]), display_mfas(Rest, Message). format_mfa({M, F, A}) -> @@ -147,6 +158,7 @@ format_fa({_M, F, A}) -> %% %% Extract an element from a tuple, or undefined if N > tuple size +%% safe_element(N, Tuple) -> case catch(element(N, Tuple)) of {'EXIT', {badarg, _}} -> @@ -155,23 +167,6 @@ safe_element(N, Tuple) -> Value end. -%% -%% Extract the line number for a given function def -%% -abstract_code_function_line(Code, Name, Args) -> - [{function, Line, Name, _, _}] = [E || E <- Code, - safe_element(1, E) == function, - safe_element(3, E) == Name, - safe_element(4, E) == Args], - Line. - -%% -%% Extract the original source filename from the abstract code -%% -abstract_code_source_file(Code) -> - [{attribute, 1, file, {Name, _}} | _] = Code, - Name. - %% %% Given a MFA, find the file and LOC where it's defined. Note that @@ -180,9 +175,13 @@ abstract_code_source_file(Code) -> %% find_mfa_source({M, F, A}) -> {M, Bin, _} = code:get_object_code(M), - {ok, {M, [{abstract_code, AbstractCode}]}} = beam_lib:chunks(Bin, [abstract_code]), - {raw_abstract_v1, Code} = AbstractCode, - Source = abstract_code_source_file(Code), - Line = abstract_code_function_line(Code, F, A), + AbstractCode = beam_lib:chunks(Bin, [abstract_code]), + {ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode, + %% Extract the original source filename from the abstract code + [{attribute, 1, file, {Source, _}} | _] = Code, + %% Extract the line number for a given function def + [{function, Line, F, _, _}] = [E || E <- Code, + safe_element(1, E) == function, + safe_element(3, E) == F, + safe_element(4, E) == A], {Source, Line}. - diff --git a/test/rebar_eunit_tests.erl b/test/rebar_eunit_tests.erl index 70e5d33..57a337a 100644 --- a/test/rebar_eunit_tests.erl +++ b/test/rebar_eunit_tests.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -119,7 +119,7 @@ environment_test_() -> ?_assert(filelib:is_dir(?TMP_DIR))}, {"Ensure the rebar script can be found, copied, and run", - [?_assert(filelib:is_file(?REBAR_SCRIPT)), + [?_assert(filelib:is_regular(?REBAR_SCRIPT)), fun assert_rebar_runs/0]}]}. assert_rebar_runs() -> @@ -236,12 +236,12 @@ assert_dirs_in(Name, [Dir|T]) -> assert_dirs_in(_, []) -> []. assert_files_in(Name, [File|T]) -> - [{Name ++ " has file: " ++ File, ?_assert(filelib:is_file(File))} | + [{Name ++ " has file: " ++ File, ?_assert(filelib:is_regular(File))} | assert_files_in(Name, T)]; assert_files_in(_, []) -> []. assert_files_not_in(Name, [File|T]) -> - [{Name ++ " does not have file: " ++ File, ?_assertNot(filelib:is_file(File))} | + [{Name ++ " does not have file: " ++ File, ?_assertNot(filelib:is_regular(File))} | assert_files_not_in(Name, T)]; assert_files_not_in(_, []) -> []. @@ -252,5 +252,5 @@ assert_full_coverage(Mod) -> string:str(X, Mod) =/= 0, string:str(X, "100%") =/= 0], file:close(F), - ?assert(length(Result) == 1) + ?assert(length(Result) =:= 1) end. diff --git a/test/rebar_file_utils_tests.erl b/test/rebar_file_utils_tests.erl index 6b87986..ea8ccf9 100644 --- a/test/rebar_file_utils_tests.erl +++ b/test/rebar_file_utils_tests.erl @@ -1,4 +1,4 @@ -%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ts=4 sw=4 et %% ------------------------------------------------------------------- %% @@ -96,7 +96,7 @@ cp_r_file_to_file_test_() -> filename:join([?TMP_DIR,"dest","new_file"])) end, fun teardown/1, - [?_assert(filelib:is_file(filename:join([?TMP_DIR,"dest","new_file"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","new_file"])))]}. cp_r_file_to_dir_test_() -> {"cp_r copies a file to directory", @@ -107,7 +107,7 @@ cp_r_file_to_dir_test_() -> filename:join([?TMP_DIR,"dest"])) end, fun teardown/1, - [?_assert(filelib:is_file(filename:join([?TMP_DIR,"dest","file1"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"])))]}. cp_r_dir_to_dir_test_() -> {"cp_r copies a directory to directory", @@ -132,7 +132,7 @@ cp_r_wildcard_file_to_dir_test_() -> filename:join([?TMP_DIR,"dest"])) end, fun teardown/1, - [?_assert(filelib:is_file(filename:join([?TMP_DIR,"dest","file1"])))]}. + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"])))]}. cp_r_wildcard_dir_to_dir_test_() -> {"cp_r copies wildcard directory to directory", @@ -215,6 +215,18 @@ cp_r_overwrite_dir_fail_test_() -> [filename:join([?TMP_DIR,"source"])], filename:join([?TMP_DIR,"dest"])))]}. +mv_file_test_() -> + {"move a file to folder", + setup, + fun() -> + setup(), + rebar_file_utils:mv(filename:join([?TMP_DIR,"source","file1"]), + filename:join([?TMP_DIR,"dest"])) + end, + fun teardown/1, + [?_assert(filelib:is_regular(filename:join([?TMP_DIR,"dest","file1"]))), + ?_assertNot(filelib:is_regular(filename:join([?TMP_DIR,"source","file1"])))]}. + %% ==================================================================== %% Utilities %% ==================================================================== @@ -254,12 +266,12 @@ teardown(_) -> %% ==================================================================== assert_files_in(Name, [File|T]) -> - [{Name ++ " has file: " ++ File, ?_assert(filelib:is_file(File))} | + [{Name ++ " has file: " ++ File, ?_assert(filelib:is_regular(File))} | assert_files_in(Name, T)]; assert_files_in(_, []) -> []. assert_files_not_in(Name, [File|T]) -> [{Name ++ " does not have file: " ++ File, - ?_assertNot(filelib:is_file(File))} | + ?_assertNot(filelib:is_regular(File))} | assert_files_not_in(Name, T)]; assert_files_not_in(_, []) -> []. diff --git a/test/upgrade_project/README.md b/test/upgrade_project/README.md new file mode 100644 index 0000000..4ab439b --- /dev/null +++ b/test/upgrade_project/README.md @@ -0,0 +1,39 @@ +#### Building version 0.1 + rebar compile + rebar generate + mv rel/dummy rel/dummy_0.1 + rebar clean + # start the release: + cd rel/dummy_0.1 + bin/dummy console + + erl> dummy_server:get_state(). + erl> dummy_server:set_state(123). + erl> dummy_server:get_state(). + +#### Building version 0.2 + + # Now, in another terminal we prepare an upgrade.. + + # change release version numbers from 0.1 to 0.2 in + $EDITOR apps/dummy/src/dummy.app.src + $EDITOR rel/reltool.config + + rebar compile + rebar generate + rebar generate-appups previous_release=dummy_0.1 + rebar generate-upgrade previous_release=dummy_0.1 + tar -zvtf rel/dummy_0.2.tar.gz + + +#### Deploying with release_handler + mv rel/dummy_0.2.tar.gz rel/dummy_0.1/releases/ + + # Now use release_handler in the running erlang console for the deploy: + + erl> release_handler:unpack_release("dummy_0.2"). + erl> release_handler:install_release("0.2"). + erl> release_handler:make_permanent("0.2"). + + erl> release_handler:which_releases(). + erl> dummy_server:get_state(). diff --git a/test/upgrade_project/apps/dummy/src/dummy.app.src b/test/upgrade_project/apps/dummy/src/dummy.app.src new file mode 100644 index 0000000..dd06752 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy.app.src @@ -0,0 +1,9 @@ +{application, dummy, [ + {description, "a dummy app"}, + {vsn, "0.1"}, + {registered, [ + dummy_app + ]}, + {mod, {dummy_app, []}}, + {applications, [kernel, stdlib, sasl]} +]}. diff --git a/test/upgrade_project/apps/dummy/src/dummy_app.erl b/test/upgrade_project/apps/dummy/src/dummy_app.erl new file mode 100644 index 0000000..51363b3 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_app.erl @@ -0,0 +1,9 @@ +-module(dummy_app). +-behaviour(application). + +-export([start/2, stop/1]). + +start(_,_) -> + dummy_sup:start_link(). + +stop(_) -> ok. diff --git a/test/upgrade_project/apps/dummy/src/dummy_server.erl b/test/upgrade_project/apps/dummy/src/dummy_server.erl new file mode 100644 index 0000000..382251e --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_server.erl @@ -0,0 +1,56 @@ +-module(dummy_server). +-behaviour(gen_server). + +-export([start_link/0, set_state/1, get_state/0]). + +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +%% + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +set_state(What) -> + gen_server:call(?MODULE, {set_state, What}). + +get_state() -> + gen_server:call(?MODULE, get_state). + + +%% + +init([]) -> + say("init, setting state to 0", []), + {ok, 0}. + + +handle_call({set_state, NewState}, _From, _State) -> + {reply, {ok, NewState}, NewState}; + +handle_call(get_state, _From, State) -> + {reply, State, State}. + +handle_cast('__not_implemented', State) -> + {noreply, State}. + +handle_info(_Info, State) -> + say("info ~p, ~p.", [_Info, State]), + {noreply, State}. + +terminate(_Reason, _State) -> + say("terminate ~p, ~p", [_Reason, _State]), + ok. + +code_change(_OldVsn, State, _Extra) -> + say("code_change ~p, ~p, ~p", [_OldVsn, State, _Extra]), + {ok, State}. + +%% Internal + +say(Format, Data) -> + io:format("~p:~p: ~s~n", [?MODULE, self(), io_lib:format(Format, Data)]). diff --git a/test/upgrade_project/apps/dummy/src/dummy_sup.erl b/test/upgrade_project/apps/dummy/src/dummy_sup.erl new file mode 100644 index 0000000..b5617c7 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_sup.erl @@ -0,0 +1,15 @@ +-module(dummy_sup). +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + Dummy = {dummy_server, + {dummy_server, start_link, []}, + permanent, 5000, worker, [dummy_server]}, + + {ok, {{one_for_one, 10, 10}, [Dummy]}}. diff --git a/test/upgrade_project/rebar.config b/test/upgrade_project/rebar.config new file mode 100644 index 0000000..11b5b9a --- /dev/null +++ b/test/upgrade_project/rebar.config @@ -0,0 +1,4 @@ +{sub_dirs, [ + "apps/dummy", + "rel" +]}. diff --git a/test/upgrade_project/rel/files/app.config b/test/upgrade_project/rel/files/app.config new file mode 100644 index 0000000..25a3bb3 --- /dev/null +++ b/test/upgrade_project/rel/files/app.config @@ -0,0 +1,10 @@ +[ + %% SASL config + {sasl, [ + {sasl_error_logger, {file, "log/sasl-error.log"}}, + {errlog_type, error}, + {error_logger_mf_dir, "log/sasl"}, % Log directory + {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size + {error_logger_mf_maxfiles, 5} % 5 files max + ]} +]. diff --git a/test/upgrade_project/rel/files/dummy b/test/upgrade_project/rel/files/dummy new file mode 100755 index 0000000..ec6d7ff --- /dev/null +++ b/test/upgrade_project/rel/files/dummy @@ -0,0 +1,155 @@ +#!/bin/bash +# -*- tab-width:4;indent-tabs-mode:nil -*- +# ex: ts=4 sw=4 et + +RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) + +RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} +RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc +RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log +PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ +RUNNER_USER= + +# 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 $@ +fi + +# Make sure CWD is set to runner base dir +cd $RUNNER_BASE_DIR + +# Make sure log directory exists +mkdir -p $RUNNER_LOG_DIR + +# Extract the target node name from node.args +NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args` +if [ -z "$NAME_ARG" ]; then + echo "vm.args needs to have either -name or -sname parameter." + exit 1 +fi + +# Extract the target cookie +COOKIE_ARG=`grep -e '-setcookie' $RUNNER_ETC_DIR/vm.args` +if [ -z "$COOKIE_ARG" ]; then + echo "vm.args needs to have a -setcookie parameter." + exit 1 +fi + +# Identify the script name +SCRIPT=`basename $0` + +# Parse out release and erts info +START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` +ERTS_VSN=${START_ERL% *} +APP_VSN=${START_ERL#* } + +# Add ERTS bin dir to our path +ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + +# Setup command to control the node +NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" + +# Check the first argument for instructions +case "$1" in + start) + # Make sure there is not already a node running + RES=`$NODETOOL ping` + if [ "$RES" = "pong" ]; then + echo "Node is already running!" + exit 1 + fi + HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" + export HEART_COMMAND + mkdir -p $PIPE_DIR + # Note the trailing slash on $PIPE_DIR/ + $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 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}'` + ;; + esac + $NODETOOL stop + while `kill -0 $PID 2>/dev/null`; + do + sleep 1 + done + ;; + + restart) + ## Restart the VM without exiting the process + $NODETOOL restart + ;; + + reboot) + ## Restart the VM completely (uses heart to restart it) + $NODETOOL reboot + ;; + + ping) + ## See if the VM is alive + $NODETOOL ping + ;; + + attach) + # Make sure a node IS running + RES=`$NODETOOL ping` + if [ "$RES" != "pong" ]; then + echo "Node is not running!" + exit 1 + fi + + shift + $ERTS_PATH/to_erl $PIPE_DIR + ;; + + console|console_clean) + # .boot file typically just $SCRIPT (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful: + case "$1" in + console) BOOTFILE=$SCRIPT ;; + console_clean) BOOTFILE=start_clean ;; + esac + # Setup beam-required vars + ROOTDIR=$RUNNER_BASE_DIR + BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\\///'` + CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo "Exec: $CMD" + echo "Root: $ROOTDIR" + + # Log the startup + logger -t "$SCRIPT[$$]" "Starting up" + + # Start the VM + exec $CMD + ;; + + *) + echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}" + exit 1 + ;; +esac + +exit 0 diff --git a/test/upgrade_project/rel/files/erl b/test/upgrade_project/rel/files/erl new file mode 100755 index 0000000..b985f23 --- /dev/null +++ b/test/upgrade_project/rel/files/erl @@ -0,0 +1,34 @@ +#!/bin/bash + +## 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. + +# Determine the abspath of where this script is executing from. +ERTS_BIN_DIR=$(cd ${0%/*} && pwd) + +# 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 +# path. +ROOTDIR=${ERTS_BIN_DIR%/*/*} + +# Parse out release and erts info +START_ERL=`cat $ROOTDIR/releases/start_erl.data` +ERTS_VSN=${START_ERL% *} +APP_VSN=${START_ERL#* } + +BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin +EMU=beam +PROGNAME=`echo $0 | sed 's/.*\\///'` +CMD="$BINDIR/erlexec" +export EMU +export ROOTDIR +export BINDIR +export PROGNAME + +exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"} diff --git a/test/upgrade_project/rel/files/nodetool b/test/upgrade_project/rel/files/nodetool new file mode 100644 index 0000000..eb08fa4 --- /dev/null +++ b/test/upgrade_project/rel/files/nodetool @@ -0,0 +1,138 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% nodetool: Helper Script for interacting with live nodes +%% +%% ------------------------------------------------------------------- + +main(Args) -> + ok = start_epmd(), + %% Extract the args + {RestArgs, TargetNode} = process_args(Args, [], undefined), + + %% 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 + {true, pong} -> + ok; + {_, pang} -> + io:format("Node ~p not responding to pings.\n", [TargetNode]), + halt(1) + end, + + case RestArgs of + ["ping"] -> + %% 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)]); + ["restart"] -> + io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); + ["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), + [RpcArgs], 60000) 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), + consult(ArgsAsString), 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + Other -> + io:format("~p\n", [Other]) + end; + Other -> + io:format("Other: ~p\n", [Other]), + io:format("Usage: nodetool {ping|stop|restart|reboot}\n") + end, + net_kernel:stop(). + +process_args([], Acc, TargetNode) -> + {lists:reverse(Acc), TargetNode}; +process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> + erlang:set_cookie(node(), list_to_atom(Cookie)), + process_args(Rest, Acc, TargetNode); +process_args(["-name", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, longnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args(["-sname", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, shortnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args([Arg | Rest], Acc, Opts) -> + process_args(Rest, [Arg | Acc], Opts). + + +start_epmd() -> + [] = os:cmd(epmd_path() ++ " -daemon"), + ok. + +epmd_path() -> + ErtsBinDir = filename:dirname(escript:script_name()), + Name = "epmd", + case os:find_executable(Name, ErtsBinDir) of + false -> + case os:find_executable(Name) of + false -> + io:format("Could not find epmd.~n"), + halt(1); + GlobalEpmd -> + GlobalEpmd + end; + Epmd -> + Epmd + end. + + +nodename(Name) -> + case string:tokens(Name, "@") of + [_Node, _Host] -> + list_to_atom(Name); + [Node] -> + [_, Host] = string:tokens(atom_to_list(node()), "@"), + list_to_atom(lists:concat([Node, "@", Host])) + end. + +append_node_suffix(Name, Suffix) -> + case string:tokens(Name, "@") of + [Node, Host] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); + [Node] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid()])) + end. + + +%% +%% Given a string or binary, parse it into a list of terms, ala file:consult/0 +%% +consult(Str) when is_list(Str) -> + consult([], Str, []); +consult(Bin) when is_binary(Bin)-> + consult([], binary_to_list(Bin), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + {ok, Term} = erl_parse:parse_term(Tokens), + consult([], Remaining, [Term | Acc]); + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end. diff --git a/test/upgrade_project/rel/files/vm.args b/test/upgrade_project/rel/files/vm.args new file mode 100644 index 0000000..7448fb1 --- /dev/null +++ b/test/upgrade_project/rel/files/vm.args @@ -0,0 +1,20 @@ + +## Name of the node +-name dummy@127.0.0.1 + +## Cookie for distributed erlang +-setcookie dummy + +## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive +## (Disabled by default..use with caution!) +##-heart + +## Enable kernel poll and a few async threads ++K true ++A 5 + +## Increase number of concurrent ports/sockets +-env ERL_MAX_PORTS 4096 + +## Tweak GC to run more often +-env ERL_FULLSWEEP_AFTER 10 diff --git a/test/upgrade_project/rel/reltool.config b/test/upgrade_project/rel/reltool.config new file mode 100644 index 0000000..22cec5e --- /dev/null +++ b/test/upgrade_project/rel/reltool.config @@ -0,0 +1,25 @@ +{sys, [ + {lib_dirs, ["../apps"]}, + {rel, "dummy", "0.1", [ + kernel, + stdlib, + sasl, + dummy + ]}, + {rel, "start_clean", "", [kernel, stdlib]}, + {boot_rel, "dummy"}, + {profile, embedded}, + {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)"]}, + {excl_archive_filters, [".*"]}, + + {app, dummy, [{incl_cond, include}]} +]}. + +{overlay, [ + {mkdir, "log/sasl"}, + {copy, "files/erl", "{{erts_vsn}}/bin/erl"}, + {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"}, + {copy, "files/dummy", "bin/dummy"}, + {copy, "files/app.config", "etc/app.config"}, + {copy, "files/vm.args", "etc/vm.args"} + ]}. |