Erlang/OTPを使ってシステムを作成する時に、一番簡単な方法はErlang/OTPをどこかにインストールすることです。次に、アプリケーション固有のコードもインストールし、アプリケーション固有のコードをコードパスに追加してErlangランタイムシステムを起動します。
Erlang/OTPシステムをそのまま使うのが、いつも最適とは限りません。開発者は、特定の目的のために新しくErlang/OTP対応のアプリケーションを作成するかもしれませんし、元々のアプリケーションのいくつかが目的と合わない、ということもあるでしょう。そのため、既存のErlang/OTPシステムを元にして、不要なアプリケーションを削除し、新しいアプリケーションを含めて、新しいシステムを作れる必要があります。また、ドキュメントとソースコードはこの配布用のシステムからは除外されます。
この章では、「ターゲットシステム」と呼ばれる、このようなシステムの構築方法について説明していきます。
このセクションでは、次のような様々な異なった要件で目標のシステムを作成していきます。
後から確認できるようにシステムからファイルにログ出力するのをサポートし、起動時に自動的にスタートする、組み込みのターゲットシステム
ここからの説明では、UNIX系のシステムでErlang/OTPを実行するものとして説明を進めて行きます。
ターゲットシステムを構成する関数を含む、 target_system.erl というErlangモジュールがあったとします。このモジュールはこれからの説明で使用します。このソースコードはこの章の最後にリストされています。
ここでは、 otp_design_principles で説明したErlang/OTPのシステム構成に合わせて、作業を進めているものと想定します。
最初に erts のバージョンと、新しいベースとなるターゲットシステムに含まれるすべてのアプリケーションの情報をリストアップして指定した、 .ref ファイル(rel(4)参照)を最初に作ります。
%% mysystem.rel
{release,
{"MYSYSTEM", "FIRST"},
{erts, "5.1"},
[{kernel, "2.7"},
{stdlib, "1.10"},
{sasl, "1.9.3"},
{pea, "1.0"}]}.
リストに含められたアプリケーションは、元々あるErlang/OTPアプリケーションだけではなく、自分で作成した新しいアプリケーションも含めます。この例では、 pea というアプリケーションが自作のアプリケーションです。
mysystem.rel ファイルのあるディレクトリから、Erlang/OTPのシステムを起動します。
os> erl -pa /home/user/target_system/myapps/pea-1.0/ebin
ここでは、 pea-1.0 の ebin ディレクトリのパスをオプションとして渡します。
ターゲットシステムの作成をします。
1> target_system:create("mysystem").
target_system:create/1 関数は次のようなことを行います。
systools:make_tar/2 を呼び出すことで、次のファイルを含む mysystem.tar.gz を生成します。
erts-5.1/bin/
releases/FIRST/start.boot
releases/mysystem.rel
lib/kernel-2.7/
lib/stdlib-1.10/
lib/sasl-1.9.3/
lib/pea-1.0/
releases/FIRST/start.boot は mysystem.boot のコピーです。また、オリジナルの mysystem.rel は、 releases ディレクトリに置かれます。
作成されたターゲットシステムを、適切なディレクトリにインストールします。
2> target_system:install("mysystem", "/usr/local/erl-target").
この target_system:install/2 関数は次のことを行います。
最後に、 releases/mysystem.rel ファイルから、ターゲットの releases/RELEASES ファイルが作られます。
ターゲットシステムの起動方法も、様々あります。
次のようにして、基本的なターゲットのシステムを起動します。
os> /usr/local/erl-target/bin/erl
このように起動すると、 kernel 、 stdlib アプリケーションだけが起動し、システムは通常の開発環境と同じようにスタートします。このように実行されるには、2つのファイルだけが必要です。 erts-5.1/bin/erl.src から取得された、 bin/erl ファイルと、 plain.boot のコピーの bin/start.boot ファイルです。
それでは、分散のシステムをスタートさせましょう。
オリジナルの mysystem.rel ファイルで指定されたすべてのアプリケーションをスタートするには、 -boot フラグを使用します。
os> /usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FIRST/start
上記のようにして、簡単にターゲットのシステムをスタートします。唯一の違いは、実行時のコード交換が行えるように、 releases/RELEASES というファイルがあるという点のみです。
To start an embedded target system the shell script bin/start is used. That shell script calls bin/run_erl, which in turn calls bin/start_erl (roughly, start_erl is an embedded variant of erl).
組み込みターゲットシステムを起動するには、 bin/start というシェルスクリプトを使用します。このシェルスクリプトは bin/run_erl を呼び出し、さらに bin/start_erl を呼び出します。おおざっぱにまとめると、 start_erl は埋め込み版の erl を呼び出します。
シェルスクリプトはここで紹介したようなことしかしないため、環境に合わせて編集すべきです。良くある使われ方としては、UNIXのシステムの起動時に一緒に起動するようにする、などです。
run_erl はランタイムシステムからのログ出力をファイルに出力するラッパーです。これはErlangシェルにアタッチする簡単なメカニズムも提供しています(to_erl)。
start_erl はルートディレクトリ(/usr/local/erl-target)、リリースディレクトリ(/usr/local/erl-target/releases)、 start_erl.data ファイルの場所を必要とします。このコマンドはシステムバージョン(“5.1”)、リリースバージョン(“FIRST”)を start_erl.data ファイルから読み出します。見つかったバージョンのランタイムシステムに、見つかったリリースバージョンのブートファイル(releases/FIRST/start.boot)を -boot フラグで指定して起動します。
start_erl は、リリースバージョンディレクトリ(releases/FIRST/sys.config)の中に sys.config がある、という想定をします。これは次のセクションの話題になります。
通常は、ユーザが start_erl シェルスクリプトに手を入れる必要はありません。
上記の説明で触れたとおり、 start_erl は、リリースバージョンディレクトリ(releases/FIRST/)の中に、 sys.config が置いてなければ正常に動作しません。もしそのファイルがなければ、システムの起動に失敗します。そのため、このファイルを追加する必要があります。
もし取り扱っているシステムのシステム設定データが、ファイルの位置やサイトに依存しないのであれば、 sys.config をなるべく早く作った方が便利です。ターゲットシステムの一部となるように、 target_system:create/1 がtarファイルを生成します。実際、 mysystem.rel ファイルだけでなく、 sys.config も作っておくと、後者のファイルは暗黙的に apropriate ディレクトリに入れられます。
上記の install/2() の流れは、通常のInstallシェルスクリプトといくつか異なっています。実際は、 create/1() はできるだけ完全なリリースパッケージを作ろうとします。 install/2() の流れから外れるのは、場所依存のファイルに対する処理を行っている時だけになります。
-module(target_system).
-include_lib("kernel/include/file.hrl").
-export([create/1, install/2]).
-define(BUFSIZE, 8192).
%% Note: RelFileName below is the *stem* without trailing .rel,
%% .script etc.
%%
%% create(RelFileName)
%%
create(RelFileName) ->
RelFile = RelFileName ++ ".rel",
io:fwrite("Reading file: \"~s\" ...~n", [RelFile]),
{ok, [RelSpec]} = file:consult(RelFile),
io:fwrite("Creating file: \"~s\" from \"~s\" ...~n",
["plain.rel", RelFile]),
{release,
{RelName, RelVsn},
{erts, ErtsVsn},
AppVsns} = RelSpec,
PlainRelSpec = {release,
{RelName, RelVsn},
{erts, ErtsVsn},
lists:filter(fun({kernel, _}) ->
true;
({stdlib, _}) ->
true;
(_) ->
false
end, AppVsns)
},
{ok, Fd} = file:open("plain.rel", [write]),
io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
file:close(Fd),
io:fwrite("Making \"plain.script\" and \"plain.boot\" files ...~n"),
make_script("plain"),
io:fwrite("Making \"~s.script\" and \"~s.boot\" files ...~n",
[RelFileName, RelFileName]),
make_script(RelFileName),
TarFileName = io_lib:fwrite("~s.tar.gz", [RelFileName]),
io:fwrite("Creating tar file \"~s\" ...~n", [TarFileName]),
make_tar(RelFileName),
io:fwrite("Creating directory \"tmp\" ...~n"),
file:make_dir("tmp"),
io:fwrite("Extracting \"~s\" into directory \"tmp\" ...~n", [TarFileName]),
extract_tar(TarFileName, "tmp"),
TmpBinDir = filename:join(["tmp", "bin"]),
ErtsBinDir = filename:join(["tmp", "erts-" ++ ErtsVsn, "bin"]),
io:fwrite("Deleting \"erl\" and \"start\" in directory \"~s\" ...~n",
[ErtsBinDir]),
file:delete(filename:join([ErtsBinDir, "erl"])),
file:delete(filename:join([ErtsBinDir, "start"])),
io:fwrite("Creating temporary directory \"~s\" ...~n", [TmpBinDir]),
file:make_dir(TmpBinDir),
io:fwrite("Copying file \"plain.boot\" to \"~s\" ...~n",
[filename:join([TmpBinDir, "start.boot"])]),
copy_file("plain.boot", filename:join([TmpBinDir, "start.boot"])),
io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
"\"~s\" to \"~s\" ...~n",
[ErtsBinDir, TmpBinDir]),
copy_file(filename:join([ErtsBinDir, "epmd"]),
filename:join([TmpBinDir, "epmd"]), [preserve]),
copy_file(filename:join([ErtsBinDir, "run_erl"]),
filename:join([TmpBinDir, "run_erl"]), [preserve]),
copy_file(filename:join([ErtsBinDir, "to_erl"]),
filename:join([TmpBinDir, "to_erl"]), [preserve]),
StartErlDataFile = filename:join(["tmp", "releases", "start_erl.data"]),
io:fwrite("Creating \"~s\" ...~n", [StartErlDataFile]),
StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
write_file(StartErlDataFile, StartErlData),
io:fwrite("Recreating tar file \"~s\" from contents in directory "
"\"tmp\" ...~n", [TarFileName]),
{ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
{ok, Cwd} = file:get_cwd(),
file:set_cwd("tmp"),
erl_tar:add(Tar, "bin", []),
erl_tar:add(Tar, "erts-" ++ ErtsVsn, []),
erl_tar:add(Tar, "releases", []),
erl_tar:add(Tar, "lib", []),
erl_tar:close(Tar),
file:set_cwd(Cwd),
io:fwrite("Removing directory \"tmp\" ...~n"),
remove_dir_tree("tmp"),
ok.
install(RelFileName, RootDir) ->
TarFile = RelFileName ++ ".tar.gz",
io:fwrite("Extracting ~s ...~n", [TarFile]),
extract_tar(TarFile, RootDir),
StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
{ok, StartErlData} = read_txt_file(StartErlDataFile),
[ErlVsn, RelVsn| _] = string:tokens(StartErlData, " \n"),
ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
BinDir = filename:join([RootDir, "bin"]),
io:fwrite("Substituting in erl.src, start.src and start_erl.src to\n"
"form erl, start and start_erl ...\n"),
subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
[{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
[preserve]),
io:fwrite("Creating the RELEASES file ...\n"),
create_RELEASES(RootDir,
filename:join([RootDir, "releases", RelFileName])).
%% LOCALS
%% make_script(RelFileName)
%%
make_script(RelFileName) ->
Opts = [no_module_tests],
systools:make_script(RelFileName, Opts).
%% make_tar(RelFileName)
%%
make_tar(RelFileName) ->
RootDir = code:root_dir(),
systools:make_tar(RelFileName, [{erts, RootDir}]).
%% extract_tar(TarFile, DestDir)
%%
extract_tar(TarFile, DestDir) ->
erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
create_RELEASES(DestDir, RelFileName) ->
release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
lists:foreach(fun(Script) ->
subst_src_script(Script, SrcDir, DestDir,
Vars, Opts)
end, Scripts).
subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
subst_file(filename:join([SrcDir, Script ++ ".src"]),
filename:join([DestDir, Script]),
Vars, Opts).
subst_file(Src, Dest, Vars, Opts) ->
{ok, Conts} = read_txt_file(Src),
NConts = subst(Conts, Vars),
write_file(Dest, NConts),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
file:write_file_info(Dest, FileInfo);
false ->
ok
end.
%% subst(Str, Vars)
%% Vars = [{Var, Val}]
%% Var = Val = string()
%% Substitute all occurrences of %Var% for Val in Str, using the list
%% of variables in Vars.
%%
subst(Str, Vars) ->
subst(Str, Vars, []).
subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
subst_var([C| Rest], Vars, Result, []);
subst([$%, C| Rest], Vars, Result) when C == $_ ->
subst_var([C| Rest], Vars, Result, []);
subst([C| Rest], Vars, Result) ->
subst(Rest, Vars, [C| Result]);
subst([], _Vars, Result) ->
lists:reverse(Result).
subst_var([$%| Rest], Vars, Result, VarAcc) ->
Key = lists:reverse(VarAcc),
case lists:keysearch(Key, 1, Vars) of
{value, {Key, Value}} ->
subst(Rest, Vars, lists:reverse(Value, Result));
false ->
subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
end;
subst_var([C| Rest], Vars, Result, VarAcc) ->
subst_var(Rest, Vars, Result, [C| VarAcc]);
subst_var([], Vars, Result, VarAcc) ->
subst([], Vars, [VarAcc ++ [$%| Result]]).
copy_file(Src, Dest) ->
copy_file(Src, Dest, []).
copy_file(Src, Dest, Opts) ->
{ok, InFd} = file:open(Src, [raw, binary, read]),
{ok, OutFd} = file:open(Dest, [raw, binary, write]),
do_copy_file(InFd, OutFd),
file:close(InFd),
file:close(OutFd),
case lists:member(preserve, Opts) of
true ->
{ok, FileInfo} = file:read_file_info(Src),
file:write_file_info(Dest, FileInfo);
false ->
ok
end.
do_copy_file(InFd, OutFd) ->
case file:read(InFd, ?BUFSIZE) of
{ok, Bin} ->
file:write(OutFd, Bin),
do_copy_file(InFd, OutFd);
eof ->
ok
end.
write_file(FName, Conts) ->
{ok, Fd} = file:open(FName, [write]),
file:write(Fd, Conts),
file:close(Fd).
read_txt_file(File) ->
{ok, Bin} = file:read_file(File),
{ok, binary_to_list(Bin)}.
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).