@ -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 | |||
setlocal | |||
set rebarscript=%0 | |||
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"} | |||
]}. |