mirror of
https://github.com/correl/rebar.git
synced 2024-11-15 03:00:18 +00:00
Merge branch 'master' of https://github.com/basho/rebar
This commit is contained in:
commit
7dc371d8a3
64 changed files with 2828 additions and 1004 deletions
50
HACKING
50
HACKING
|
@ -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
|
135
README.md
Normal file
135
README.md
Normal file
|
@ -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.
|
11
THANKS
11
THANKS
|
@ -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'
|
||||
|
|
2
TODO
2
TODO
|
@ -1,2 +0,0 @@
|
|||
* write documentation
|
||||
* ZSH completion script
|
12
bootstrap
12
bootstrap
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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}) )
|
||||
|
|
167
priv/templates/ctsuite.erl
Normal file
167
priv/templates/ctsuite.erl
Normal file
|
@ -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."}.
|
2
priv/templates/ctsuite.template
Normal file
2
priv/templates/ctsuite.template
Normal file
|
@ -0,0 +1,2 @@
|
|||
{variables, [{testmod, "mymodule"}]}.
|
||||
{template, "ctsuite.erl", "test/{{testmod}}_SUITE.erl"}.
|
|
@ -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.
|
||||
|
|
|
@ -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=% %*
|
||||
|
|
|
@ -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}.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
308
src/rebar.erl
308
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.
|
||||
|
|
109
src/rebar_abnfc_compiler.erl
Normal file
109
src/rebar_abnfc_compiler.erl
Normal file
|
@ -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.
|
|
@ -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}.
|
||||
|
|
179
src/rebar_appups.erl
Normal file
179
src/rebar_appups.erl
Normal file
|
@ -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.
|
|
@ -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
|
||||
%% -------------------------------------------------------------------
|
||||
%%
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
%% -------------------------------------------------------------------
|
||||
%%
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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,
|
||||
{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,
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
||||
is_quickcheck_avail() ->
|
||||
case erlang:get(is_quickcheck_avail) of
|
||||
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_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", []),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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] ++
|
||||
|
|
|
@ -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: ".
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")]).
|
||||
|
|
|
@ -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),
|
||||
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
|
||||
%% 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}];
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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}.
|
||||
|
||||
|
|
|
@ -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)]),
|
||||
|
||||
%% Now, use our context to process the template definition -- this permits us to
|
||||
%% use variables within the definition for filenames.
|
||||
%% Handle variables that possibly include other variables in their
|
||||
%% definition
|
||||
Context = resolve_recursive_vars(dict:to_list(Context1), Context1),
|
||||
|
||||
?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].
|
||||
|
||||
|
@ -205,6 +215,18 @@ update_vars([Key | Rest], Dict) ->
|
|||
update_vars(Rest, dict:store(Key, Value, 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
|
||||
%%
|
||||
|
@ -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).
|
||||
|
|
186
src/rebar_upgrade.erl
Normal file
186
src/rebar_upgrade.erl
Normal file
|
@ -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).
|
|
@ -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.
|
||||
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}.
|
||||
|
||||
sh_loop(Port) ->
|
||||
-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.
|
||||
|
|
|
@ -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}.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(_, []) -> [].
|
||||
|
|
39
test/upgrade_project/README.md
Normal file
39
test/upgrade_project/README.md
Normal file
|
@ -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().
|
9
test/upgrade_project/apps/dummy/src/dummy.app.src
Normal file
9
test/upgrade_project/apps/dummy/src/dummy.app.src
Normal file
|
@ -0,0 +1,9 @@
|
|||
{application, dummy, [
|
||||
{description, "a dummy app"},
|
||||
{vsn, "0.1"},
|
||||
{registered, [
|
||||
dummy_app
|
||||
]},
|
||||
{mod, {dummy_app, []}},
|
||||
{applications, [kernel, stdlib, sasl]}
|
||||
]}.
|
9
test/upgrade_project/apps/dummy/src/dummy_app.erl
Normal file
9
test/upgrade_project/apps/dummy/src/dummy_app.erl
Normal file
|
@ -0,0 +1,9 @@
|
|||
-module(dummy_app).
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
start(_,_) ->
|
||||
dummy_sup:start_link().
|
||||
|
||||
stop(_) -> ok.
|
56
test/upgrade_project/apps/dummy/src/dummy_server.erl
Normal file
56
test/upgrade_project/apps/dummy/src/dummy_server.erl
Normal file
|
@ -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)]).
|
15
test/upgrade_project/apps/dummy/src/dummy_sup.erl
Normal file
15
test/upgrade_project/apps/dummy/src/dummy_sup.erl
Normal file
|
@ -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]}}.
|
4
test/upgrade_project/rebar.config
Normal file
4
test/upgrade_project/rebar.config
Normal file
|
@ -0,0 +1,4 @@
|
|||
{sub_dirs, [
|
||||
"apps/dummy",
|
||||
"rel"
|
||||
]}.
|
10
test/upgrade_project/rel/files/app.config
Normal file
10
test/upgrade_project/rel/files/app.config
Normal file
|
@ -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
|
||||
]}
|
||||
].
|
155
test/upgrade_project/rel/files/dummy
Executable file
155
test/upgrade_project/rel/files/dummy
Executable file
|
@ -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
|
34
test/upgrade_project/rel/files/erl
Executable file
34
test/upgrade_project/rel/files/erl
Executable file
|
@ -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+"$@"}
|
138
test/upgrade_project/rel/files/nodetool
Normal file
138
test/upgrade_project/rel/files/nodetool
Normal file
|
@ -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.
|
20
test/upgrade_project/rel/files/vm.args
Normal file
20
test/upgrade_project/rel/files/vm.args
Normal file
|
@ -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
|
25
test/upgrade_project/rel/reltool.config
Normal file
25
test/upgrade_project/rel/reltool.config
Normal file
|
@ -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"}
|
||||
]}.
|
Loading…
Reference in a new issue