Table Of Contents

Previous topic

2. エラーのロギング

Next topic

1. イントロダクション

This Page

3. 最初のターゲットシステムの作成

3.1. イントロダクション

Erlang/OTPを使ってシステムを作成する時に、一番簡単な方法はErlang/OTPをどこかにインストールすることです。次に、アプリケーション固有のコードもインストールし、アプリケーション固有のコードをコードパスに追加してErlangランタイムシステムを起動します。

Erlang/OTPシステムをそのまま使うのが、いつも最適とは限りません。開発者は、特定の目的のために新しくErlang/OTP対応のアプリケーションを作成するかもしれませんし、元々のアプリケーションのいくつかが目的と合わない、ということもあるでしょう。そのため、既存のErlang/OTPシステムを元にして、不要なアプリケーションを削除し、新しいアプリケーションを含めて、新しいシステムを作れる必要があります。また、ドキュメントとソースコードはこの配布用のシステムからは除外されます。

この章では、「ターゲットシステム」と呼ばれる、このようなシステムの構築方法について説明していきます。

このセクションでは、次のような様々な異なった要件で目標のシステムを作成していきます。

  • 通常のerlスクリプトを呼んで起動する、基本的なターゲットシステム
  • 実行時にコードの交換が実行できる、シンプルなターゲットシステム

後から確認できるようにシステムからファイルにログ出力するのをサポートし、起動時に自動的にスタートする、組み込みのターゲットシステム

ここからの説明では、UNIX系のシステムでErlang/OTPを実行するものとして説明を進めて行きます。

ターゲットシステムを構成する関数を含む、 target_system.erl というErlangモジュールがあったとします。このモジュールはこれからの説明で使用します。このソースコードはこの章の最後にリストされています。

3.2. ターゲットシステムの作成

ここでは、 otp_design_principles で説明したErlang/OTPのシステム構成に合わせて、作業を進めているものと想定します。

3.2.1. ステップ1

最初に 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 というアプリケーションが自作のアプリケーションです。

3.2.2. ステップ2

mysystem.rel ファイルのあるディレクトリから、Erlang/OTPのシステムを起動します。

os> erl -pa /home/user/target_system/myapps/pea-1.0/ebin

ここでは、 pea-1.0ebin ディレクトリのパスをオプションとして渡します。

3.2.3. ステップ3

ターゲットシステムの作成をします。

1> target_system:create("mysystem").

target_system:create/1 関数は次のようなことを行います。

  • mysystem.rel ファイルを読み込み、新しい plain.rel これは、 kernelstdlib アプリケーションだけがリストに入っているというのを除いて、前者と同じです。
  • systools:make_script/2 を呼び出し、 mysystem.relplain.rel ファイルから、 mysystem.scriptmysystem.bootplain.scriptplain.boot を作成します。
  • 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.bootmysystem.boot のコピーです。また、オリジナルの mysystem.rel は、 releases ディレクトリに置かれます。

  • 一時ディレクトリの tmp を作成し、 mysystem.tar.gz ファイルを展開します。
  • tmp/erts-5.1/bin から erlstart ファイルを削除します。
  • tmp/bin ディレクトリを作成します。
  • 生成されたファイルの plain.boot を、 tmp/bin/start.boot としてコピーします。
  • epmdrun_erland to_erl を、 tmp/erts-5.1/bin から tmp/bin にコピーします。
  • 5.1 FIRST というテキストを含む、 tmp/releases/start_erl.data を作成します。
  • tmp ディレクトリから、再び mysystem.tar.gz を作成し、 tmp ディレクトリを削除します。

3.3. ターゲットシステムのインストール

3.3.1. ステップ4

作成されたターゲットシステムを、適切なディレクトリにインストールします。

2> target_system:install("mysystem", "/usr/local/erl-target").

この target_system:install/2 関数は次のことを行います。

  • mysystem.tar.gz を、ターゲットディレクトリの /usr/local/erl-target に展開します。
  • Erlangのランタイムシステムのバージョン 5.1 を探すために、ターゲットディレクトリ内で releases/start_erl.data を読みます。
  • %FINAL_ROOTDIR%%EMU% を、それぞれ、 /usr/local/erl-targebeam に読み替えて、ターゲットの erts-5.1/bin ディレクトリの中の、 erl.srcstart.srcstart_erl.src 内で、結果のファイルの erlstartrun_erl をターゲットの bin ディレクトリの中に置きます。

最後に、 releases/mysystem.rel ファイルから、ターゲットの releases/RELEASES ファイルが作られます。

3.4. ターゲットシステムの起動

ターゲットシステムの起動方法も、様々あります。

次のようにして、基本的なターゲットのシステムを起動します。

os> /usr/local/erl-target/bin/erl

このように起動すると、 kernelstdlib アプリケーションだけが起動し、システムは通常の開発環境と同じようにスタートします。このように実行されるには、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 シェルスクリプトに手を入れる必要はありません。

3.5. システム設定パラメータ

上記の説明で触れたとおり、 start_erl は、リリースバージョンディレクトリ(releases/FIRST/)の中に、 sys.config が置いてなければ正常に動作しません。もしそのファイルがなければ、システムの起動に失敗します。そのため、このファイルを追加する必要があります。

もし取り扱っているシステムのシステム設定データが、ファイルの位置やサイトに依存しないのであれば、 sys.config をなるべく早く作った方が便利です。ターゲットシステムの一部となるように、 target_system:create/1 がtarファイルを生成します。実際、 mysystem.rel ファイルだけでなく、 sys.config も作っておくと、後者のファイルは暗黙的に apropriate ディレクトリに入れられます。

3.6. インストールスクリプトとの違い

上記の install/2() の流れは、通常のInstallシェルスクリプトといくつか異なっています。実際は、 create/1() はできるだけ完全なリリースパッケージを作ろうとします。 install/2() の流れから外れるのは、場所依存のファイルに対する処理を行っている時だけになります。

3.7. target_system.erlのソースコード

-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).