@ -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 |
@ -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. |
@ -1,2 +0,0 @@ | |||||
* write documentation | |||||
* ZSH completion script |
@ -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."}. |
@ -0,0 +1,2 @@ | |||||
{variables, [{testmod, "mymodule"}]}. | |||||
{template, "ctsuite.erl", "test/{{testmod}}_SUITE.erl"}. |
@ -1,3 +1,4 @@ | |||||
@echo off | @echo off | ||||
setlocal | |||||
set rebarscript=%0 | set rebarscript=%0 | ||||
escript.exe %rebarscript:.bat=% %* | escript.exe %rebarscript:.bat=% %* |
@ -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. |
@ -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. |
@ -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). |
@ -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(). |
@ -0,0 +1,9 @@ | |||||
{application, dummy, [ | |||||
{description, "a dummy app"}, | |||||
{vsn, "0.1"}, | |||||
{registered, [ | |||||
dummy_app | |||||
]}, | |||||
{mod, {dummy_app, []}}, | |||||
{applications, [kernel, stdlib, sasl]} | |||||
]}. |
@ -0,0 +1,9 @@ | |||||
-module(dummy_app). | |||||
-behaviour(application). | |||||
-export([start/2, stop/1]). | |||||
start(_,_) -> | |||||
dummy_sup:start_link(). | |||||
stop(_) -> ok. |
@ -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)]). |
@ -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]}}. |
@ -0,0 +1,4 @@ | |||||
{sub_dirs, [ | |||||
"apps/dummy", | |||||
"rel" | |||||
]}. |
@ -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 | |||||
]} | |||||
]. |
@ -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 |
@ -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+"$@"} |
@ -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. |
@ -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 |
@ -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"} | |||||
]}. |