ビルドツールはrebar
rebarは実行バイナリ作成、テスト実行、依存モジュール管理など雑多なタスクを楽にしてくれます。version 3を使いましょう。
rebarを入手
パスが通っているフォルダへダウンロードします。
前回の「今時のErlangプロジェクトの作り方(1/3)」の例に沿っている場合は、~/bin に移動して以下のコマンドを実行します。
wget https://s3.amazonaws.com/rebar3/rebar3
chmod 0755 rebar3
プロジェクトを作る
rebar3 new release myapp
以下のようなファイルが作られます。
myapp
├── LICENSE
├── README.md
├── apps
│ └── myapp
│ └── src
│ ├── myapp.app.src # 起動時に読み込むモジュール列挙など
│ ├── myapp_app.erl # ここに処理を書いていきます
│ └── myapp_sup.erl # myapp_app.erlのsupervisor処理
├── config
│ ├── sys.config # 設定情報をここに記載、依存モジュールの設定もここで行う
│ └── vm.args # Erlangの起動オプション
└── rebar.config
実装する
ちょっとしたプログラムを書いてみます。
myapp/src/myapp_hello.erl
-module(myapp_hello).
-behavior(gen_server).
-define(DELAY, 1000).
-export([
start_link/0,
stop/0,
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2
]).
start_link() ->
io:format("- start_link() ~n"),
gen_server:start_link({local, myapp_hello}, ?MODULE, [], []).
stop() ->
io:format("- stop() ~n"),
gen_server:call(?MODULE, stop).
init(Args) ->
io:format("- init(~p) ~n", [Args]),
io:format("hello world"),
{ok, {}}.
handle_call(stop, From, State) ->
io:format("- handle_call(stop, ~p, ~p) ~n", [From, State]),
{stop, normal, ok, State};
handle_call(Message, From, State) ->
io:format("- handle_call(~p, ~p, ~p) ~n", [Message, From, State]),
{reply, ok, State, ?DELAY}.
handle_cast(Message, State) ->
io:format("- handle_cast(~p, ~p) ~n", [Message, State]),
{noreply, State, ?DELAY}.
handle_info(timeout, State) ->
io:format("- handle_info(timeout, ~p) ~n", [State]),
{noreply, State, ?DELAY};
handle_info(Message, State) ->
io:format("- handle_info(~p, ~p) ~n", [Message, State]),
{noreply, State, ?DELAY}.
code_change(OldVsn, State, Extra) ->
io:format("- code_change(~p, ~p, ~p) ~n", [OldVsn, State, Extra]),
{ok, State}.
terminate(Reason, State) ->
io:format("~p terminate ~p~n", [Reason, State]).
```
先程のプログラムを起動するためsupervisorも変更します。
変更箇所はinit()の中身です。
```erlang:myapp_sup.erl
%%%-------------------------------------------------------------------
%% @doc myapp top level supervisor.
%% @end
%%%-------------------------------------------------------------------
-module(myapp_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API functions
%%====================================================================
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules}
init([]) ->
{ok, { {one_for_one, 0, 1},
[
{myapp_hello, {myapp_hello, start_link, []}, temporary, 1000, worker, [myapp_hello]}
]} }.
%%====================================================================
%% Internal functions
%%====================================================================
```
## ビルドと実行
ビルド方法です。
```shell-session
rebar3 compile
```
以下のファイルが作成されます。
```text
_build
└── default
└── lib
└── myapp
├── ebin
│ ├── myapp.app
│ ├── myapp_app.beam
│ ├── myapp_hello.beam
│ └── myapp_sup.beam
├── include -> ../../../../apps/myapp/include
├── priv -> ../../../../apps/myapp/priv
└── src -> ../../../../apps/myapp/src
```
erlコマンドで実行してみましょう。[^1]
```text
% erl -pa _build/default/lib/myapp/ebin
1> {ok, Pid} = myapp_hello:start_link().
- start_link()
- init([])
hello world{ok,<0.59.0>}
2> gen_server:call(Pid, knock).
- handle_call(knock, {<0.57.0>,#Ref<0.0.4.111>}, {})
{}
3> gen_server:call(Pid, stop).
- handle_call(stop, {<0.57.0>,#Ref<0.0.4.117>}, 1000)
normal terminate 1000
ok
```
## リリースビルド
リリースビルドを行うと実行バイナリも作成されます。
```shell-session
rebar3 release
```
以下のファイルが作られます。
```text
_build
└── default
├── lib
│ └── myapp
│ ├── ebin
│ │ ├── myapp.app
│ │ ├── myapp_app.beam
│ │ ├── myapp_hello.beam
│ │ └── myapp_sup.beam
│ ├── include -> ../../../../apps/myapp/include
│ ├── priv -> ../../../../apps/myapp/priv
│ └── src -> ../../../../apps/myapp/src
└── rel
└── myapp
├── bin
│ ├── install_upgrade.escript
│ ├── myapp
│ ├── myapp-0.1.0
│ ├── nodetool
│ └── start_clean.boot
├── lib
│ └── myapp-0.1.0 -> /home/tadokoro/git/erlang_start/myapp/_build/default/lib/myapp
└── releases
├── 0.1.0
│ ├── myapp.boot
│ ├── myapp.rel
│ ├── myapp.script
│ ├── start_clean.boot
│ ├── sys.config.orig -> /home/tadokoro/git/erlang_start/myapp/config/sys.config
│ └── vm.args.orig -> /home/tadokoro/git/erlang_start/myapp/config/vm.args
├── RELEASES
└── start_erl.data
```
### 実行
リリースビルドで作成されたファイルを実行してみます。[^1]
```shell-session
% ./_build/default/rel/myapp/bin/myapp console
- start_link()
- init([])
hello world
(myapp@tvb)1> gen_server:call(myapp_hello, knock).
- handle_call(knock, {<0.226.0>,#Ref<0.0.1.167>}, {})
{}
(myapp@tvb)2> gen_server:call(myapp_hello, stop).
- handle_call(stop, {<0.226.0>,#Ref<0.0.1.173>}, 1000)
normal terminate 1000
ok
```
### バックグランド実行
サーバ環境で動かすのに便利なバックグラウンド実行をしてみます。
```shell-session
./_build/default/rel/myapp/bin/myapp start
```
psコマンドでバックグランド動作している事が確認できます。[^1]
```shell-session
PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
3577 0.0 0.4 3216888 33408 pts/20 Ssl+ 12:02 0:00 ~/local/bin/erlang/19.1/erts-8.1/bin/beam.smp -K true -A30 -- -root ~/myapp/_build/default/rel/myapp -progname ~/myapp/_build/default/rel/myapp/bin/myapp -- -home ~/ -- -boot ~/myapp/_build/default/rel/myapp/releases/0.1.0/myapp -mode embedded -boot_var ERTS_LIB_DIR ~/local/bin/erlang/19.1/erts-8.1/../lib -config ~/myapp/_build/default/rel/myapp/releases/0.1.0/sys.config -sname myapp -setcookie myapp_cookie -- console
```
停止します。
```shell-session
./_build/default/rel/myapp/bin/myapp stop
```
## まとめ
お疲れ様でした。
これで使った開発、リリースビルドが行えるようになりました。
次回はrebarを使ったテスト、依存関係の解決などをご紹介します。
[^1]: 見やすいよう出力を一部省略・変更しています。