1
<?xml version="1.0" encoding="latin1" ?>
2
<!DOCTYPE chapter SYSTEM "chapter.dtd">
7
<year>2002</year><year>2009</year>
8
<holder>Ericsson AB. All Rights Reserved.</holder>
11
The contents of this file are subject to the Erlang Public License,
12
Version 1.1, (the "License"); you may not use this file except in
13
compliance with the License. You should have received a copy of the
14
Erlang Public License along with this software. If not, it can be
15
retrieved online at http://www.erlang.org/.
17
Software distributed under the License is distributed on an "AS IS"
18
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
19
the License for the specific language governing rights and limitations
24
<title>Creating a First Target System</title>
25
<prepared>Peter Högfeldt</prepared>
26
<responsible></responsible>
30
<date>2002-09-17</date>
32
<file>create_target.xml</file>
36
<title>Introduction</title>
37
<p>When creating a system using Erlang/OTP, the most simple way is
38
to install Erlang/OTP somewhere, install the application specific
39
code somewhere else, and then start the Erlang runtime system,
40
making sure the code path includes the application specific code.</p>
41
<p>Often it is not desirable to use an Erlang/OTP system as is. A
42
developer may create new Erlang/OTP compliant applications for a
43
particular purpose, and several original Erlang/OTP applications
44
may be irrelevant for the purpose in question. Thus, there is a
45
need to be able to create a new system based on a given
46
Erlang/OTP system, where dispensable applications are removed,
47
and a set of new applications that are included in the new
48
system. Documentation and source code is irrelevant and is
49
therefore not included in the new system.</p>
50
<p>This chapter is about creating such a system, which we call a
51
<em>target system</em>.</p>
52
<p>In the following sections we consider creating target systems with
53
different requirements of functionality:</p>
54
<list type="bulleted">
55
<item>a <em>basic target system</em> that can be started by
56
calling the ordinary <c>erl</c> script, </item>
57
<item>a <em>simple target system</em> where also code
58
replacement in run-time can be performed, and</item>
59
<item>an <em>embedded target system</em> where there is also
60
support for logging output from the system to file for later
61
inspection, and where the system can be started automatically
64
<p>We only consider the case when Erlang/OTP is running on a UNIX
66
<p>There is an example Erlang module <c>target_system.erl</c> that
67
contains functions for creating and installing a target system.
68
That module is used in the examples below. The source code of
69
the module is listed at the end of this chapter.</p>
73
<title>Creating a Target System</title>
74
<p>It is assumed that you have a working Erlang/OTP system structured
75
according to the OTP Design Principles.</p>
76
<p><em>Step 1.</em> First create a <c>.rel</c> file (see
77
<c>rel(4)</c>) that specifies the <c>erts</c> version
78
and lists all applications that should be included in the new
79
basic target system. An example is the following
80
<c>mysystem.rel</c> file:</p>
84
{"MYSYSTEM", "FIRST"},
89
{pea, "1.0"}]}. </code>
90
<p>The listed applications are not only original Erlang/OTP
91
applications but possibly also new applications that you have
92
written yourself (here examplified by the application
94
<p><em>Step 2.</em> From the directory where the <c>mysystem.rel</c>
95
file reside, start the Erlang/OTP system:</p>
97
os> <input>erl -pa /home/user/target_system/myapps/pea-1.0/ebin</input></pre>
98
<p>where also the path to the <c>pea-1.0</c> ebin directory is
100
<p><em>Step 3.</em> Now create the target system: </p>
102
1> <input>target_system:create("mysystem").</input></pre>
103
<p>The <c>target_system:create/1</c> function does the following:</p>
104
<list type="ordered">
105
<item>Reads the <c>mysystem.rel</c> file, and creates a new file
106
<c>plain.rel</c> which is identical to former, except that it
107
only lists the <c>kernel</c> and <c>stdlib</c> applications. </item>
108
<item>From the <c>mysystem.rel</c> and <c>plain.rel</c> files
109
creates the files <c>mysystem.script</c>,
110
<c>mysystem.boot</c>, <c>plain.script</c>, and
111
<c>plain.boot</c> through a call to
112
<c>systools:make_script/2</c>.</item>
114
<p>Creates the file <c>mysystem.tar.gz</c> by a call to
115
<c>systools:make_tar/2</c>. That file has the following
119
releases/FIRST/start.boot
120
releases/mysystem.rel
125
<p>The file <c>releases/FIRST/start.boot</c> is a copy of our
126
<c>mysystem.boot</c>, and a copy of the original
127
<c>mysystem.rel</c> has been put in the <c>releases</c>
130
<item>Creates the temporary directory <c>tmp</c> and extracts the tar file
131
<c>mysystem.tar.gz</c> into that directory. </item>
132
<item>Deletes the <c>erl</c> and <c>start</c> files from
133
<c>tmp/erts-5.1/bin</c>. XXX Why.</item>
134
<item>Creates the directory <c>tmp/bin</c>.</item>
135
<item>Copies the previously creates file <c>plain.boot</c> to
136
<c>tmp/bin/start.boot</c>.</item>
137
<item>Copies the files <c>epmd</c>, <c>run_erl</c>, and
138
<c>to_erl</c> from the directory <c>tmp/erts-5.1/bin</c> to
139
the directory <c>tmp/bin</c>.</item>
140
<item>Creates the file <c>tmp/releases/start_erl.data</c> with the
141
contents "5.1 FIRST".
143
<item>Recreates the file <c>mysystem.tar.gz</c> from the directories
144
in the directory <c>tmp</c>, and removes <c>tmp</c>.</item>
149
<title>Installing a Target System</title>
150
<p><em>Step 4.</em> Install the created target system in a
151
suitable directory. </p>
153
2> <input>target_system:install("mysystem", "/usr/local/erl-target").</input></pre>
154
<p>The function <c>target_system:install/2</c> does the following:
156
<list type="ordered">
157
<item>Extracts the tar file <c>mysystem.tar.gz</c> into the target
158
directory <c>/usr/local/erl-target</c>.</item>
159
<item>In the target directory reads the file <c>releases/start_erl.data</c>
160
in order to find the Erlang runtime system version ("5.1").</item>
161
<item>Substitutes <c>%FINAL_ROOTDIR%</c> and <c>%EMU%</c> for
162
<c>/usr/local/erl-target</c> and <c>beam</c>, respectively, in
163
the files <c>erl.src</c>, <c>start.src</c>, and
164
<c>start_erl.src</c> of the target <c>erts-5.1/bin</c>
165
directory, and puts the resulting files <c>erl</c>,
166
<c>start</c>, and <c>run_erl</c> in the target <c>bin</c>
168
<item>Finally the target <c>releases/RELEASES</c> file is created
169
from data in the <c>releases/mysystem.rel</c> file.</item>
174
<title>Starting a Target System</title>
175
<p>Now we have a target system that can be started in various ways.</p>
176
<p>We start it as a <em>basic target system</em> by invoking</p>
178
os> <input>/usr/local/erl-target/bin/erl</input></pre>
179
<p>where only the <c>kernel</c> and <c>stdlib</c> applications are
180
started, i.e. the system is started as an ordinary development
181
system. There are only two files needed for all this to work:
182
<c>bin/erl</c> file (obtained from <c>erts-5.1/bin/erl.src</c>)
183
and the <c>bin/start.boot</c> file (a copy of <c>plain.boot</c>).</p>
184
<p>We can also start a distributed system (requires <c>bin/epmd</c>).</p>
185
<p>To start all applications specified in the original
186
<c>mysystem.rel</c> file, use the <c>-boot</c> flag as follows:</p>
188
os> <input>/usr/local/erl-target/bin/erl -boot /usr/local/erl-target/releases/FIRST/start</input></pre>
189
<p>We start a <em>simple target system</em> as above. The only difference
190
is that also the file <c>releases/RELEASES</c> is present for
191
code replacement in run-time to work.</p>
192
<p>To start an <em>embedded target system</em> the shell script
193
<c>bin/start</c> is used. That shell script calls
194
<c>bin/run_erl</c>, which in turn calls <c>bin/start_erl</c>
195
(roughly, <c>start_erl</c> is an embedded variant of
197
<p>The shell script <c>start</c> is only an example. You should
198
edit it to suite your needs. Typically it is executed when the
199
UNIX system boots.</p>
200
<p><c>run_erl</c> is a wrapper that provides logging of output from
201
the run-time system to file. It also provides a simple mechanism
202
for attaching to the Erlang shell (<c>to_erl</c>).</p>
203
<p><c>start_erl</c> requires the root directory
204
(<c>"/usr/local/erl-target"</c>), the releases directory
205
(<c>"/usr/local/erl-target/releases"</c>), and the location of
206
the <c>start_erl.data</c> file. It reads the run-time system
207
version (<c>"5.1"</c>) and release version (<c>"FIRST"</c>) from
208
the <c>start_erl.data</c> file, starts the run-time system of the
209
version found, and provides <c>-boot</c> flag specifying the boot
210
file of the release version found
211
(<c>"releases/FIRST/start.boot"</c>).</p>
212
<p><c>start_erl</c> also assumes that there is <c>sys.config</c> in
213
release version directory (<c>"releases/FIRST/sys.config</c>). That
214
is the topic of the next section (see below).</p>
215
<p>The <c>start_erl</c> shell script should normally not be
216
altered by the user.</p>
220
<title>System Configuration Parameters</title>
221
<p>As was pointed out above <c>start_erl</c> requires a
222
<c>sys.config</c> in the release version directory
223
(<c>"releases/FIRST/sys.config"</c>). If there is no such a
224
file, the system start will fail. Hence such a file has to
227
<p>If you have system configuration data that are neither file
228
location dependent nor site dependent, it may be convenient to
229
create the <c>sys.config</c> early, so that it becomes a part of
230
the target system tar file created by
231
<c>target_system:create/1</c>. In fact, if you create, in the
232
current directory, not only the <c>mysystem.rel</c> file, but
233
also a <c>sys.config</c> file, that latter file will be tacitly
234
put in the apropriate directory.</p>
238
<title>Differences from the Install Script</title>
239
<p>The above <c>install/2</c> procedure differs somewhat from that
240
of the ordinary <c>Install</c> shell script. In fact, <c>create/1</c>
241
makes the release package as complete as possible, and leave to the
242
<c>install/2</c> procedure to finish by only considering location
247
<title>Listing of target_system.erl</title>
248
<code type="none"><![CDATA[
249
-module(target_system).
250
-include_lib("kernel/include/file.hrl").
251
-export([create/1, install/2]).
252
-define(BUFSIZE, 8192).
254
%% Note: RelFileName below is the *stem* without trailing .rel,
258
%% create(RelFileName)
260
create(RelFileName) ->
261
RelFile = RelFileName ++ ".rel",
262
io:fwrite("Reading file: \"~s\" ...~n", [RelFile]),
263
{ok, [RelSpec]} = file:consult(RelFile),
264
io:fwrite("Creating file: \"~s\" from \"~s\" ...~n",
265
["plain.rel", RelFile]),
270
PlainRelSpec = {release,
273
lists:filter(fun({kernel, _}) ->
281
{ok, Fd} = file:open("plain.rel", [write]),
282
io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
285
io:fwrite("Making \"plain.script\" and \"plain.boot\" files ...~n"),
286
make_script("plain"),
288
io:fwrite("Making \"~s.script\" and \"~s.boot\" files ...~n",
289
[RelFileName, RelFileName]),
290
make_script(RelFileName),
292
TarFileName = io_lib:fwrite("~s.tar.gz", [RelFileName]),
293
io:fwrite("Creating tar file \"~s\" ...~n", [TarFileName]),
294
make_tar(RelFileName),
296
io:fwrite("Creating directory \"tmp\" ...~n"),
297
file:make_dir("tmp"),
299
io:fwrite("Extracting \"~s\" into directory \"tmp\" ...~n", [TarFileName]),
300
extract_tar(TarFileName, "tmp"),
302
TmpBinDir = filename:join(["tmp", "bin"]),
303
ErtsBinDir = filename:join(["tmp", "erts-" ++ ErtsVsn, "bin"]),
304
io:fwrite("Deleting \"erl\" and \"start\" in directory \"~s\" ...~n",
306
file:delete(filename:join([ErtsBinDir, "erl"])),
307
file:delete(filename:join([ErtsBinDir, "start"])),
309
io:fwrite("Creating temporary directory \"~s\" ...~n", [TmpBinDir]),
310
file:make_dir(TmpBinDir),
312
io:fwrite("Copying file \"plain.boot\" to \"~s\" ...~n",
313
[filename:join([TmpBinDir, "start.boot"])]),
314
copy_file("plain.boot", filename:join([TmpBinDir, "start.boot"])),
316
io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
317
"\"~s\" to \"~s\" ...~n",
318
[ErtsBinDir, TmpBinDir]),
319
copy_file(filename:join([ErtsBinDir, "epmd"]),
320
filename:join([TmpBinDir, "epmd"]), [preserve]),
321
copy_file(filename:join([ErtsBinDir, "run_erl"]),
322
filename:join([TmpBinDir, "run_erl"]), [preserve]),
323
copy_file(filename:join([ErtsBinDir, "to_erl"]),
324
filename:join([TmpBinDir, "to_erl"]), [preserve]),
326
StartErlDataFile = filename:join(["tmp", "releases", "start_erl.data"]),
327
io:fwrite("Creating \"~s\" ...~n", [StartErlDataFile]),
328
StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
329
write_file(StartErlDataFile, StartErlData),
331
io:fwrite("Recreating tar file \"~s\" from contents in directory "
332
"\"tmp\" ...~n", [TarFileName]),
333
{ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
334
{ok, Cwd} = file:get_cwd(),
336
erl_tar:add(Tar, "bin", []),
337
erl_tar:add(Tar, "erts-" ++ ErtsVsn, []),
338
erl_tar:add(Tar, "releases", []),
339
erl_tar:add(Tar, "lib", []),
342
io:fwrite("Removing directory \"tmp\" ...~n"),
343
remove_dir_tree("tmp"),
347
install(RelFileName, RootDir) ->
348
TarFile = RelFileName ++ ".tar.gz",
349
io:fwrite("Extracting ~s ...~n", [TarFile]),
350
extract_tar(TarFile, RootDir),
351
StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
352
{ok, StartErlData} = read_txt_file(StartErlDataFile),
353
[ErlVsn, RelVsn| _] = string:tokens(StartErlData, " \n"),
354
ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
355
BinDir = filename:join([RootDir, "bin"]),
356
io:fwrite("Substituting in erl.src, start.src and start_erl.src to\n"
357
"form erl, start and start_erl ...\n"),
358
subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
359
[{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
361
io:fwrite("Creating the RELEASES file ...\n"),
362
create_RELEASES(RootDir,
363
filename:join([RootDir, "releases", RelFileName])).
367
%% make_script(RelFileName)
369
make_script(RelFileName) ->
370
Opts = [no_module_tests],
371
systools:make_script(RelFileName, Opts).
373
%% make_tar(RelFileName)
375
make_tar(RelFileName) ->
376
RootDir = code:root_dir(),
377
systools:make_tar(RelFileName, [{erts, RootDir}]).
379
%% extract_tar(TarFile, DestDir)
381
extract_tar(TarFile, DestDir) ->
382
erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
384
create_RELEASES(DestDir, RelFileName) ->
385
release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
387
subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
388
lists:foreach(fun(Script) ->
389
subst_src_script(Script, SrcDir, DestDir,
393
subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
394
subst_file(filename:join([SrcDir, Script ++ ".src"]),
395
filename:join([DestDir, Script]),
398
subst_file(Src, Dest, Vars, Opts) ->
399
{ok, Conts} = read_txt_file(Src),
400
NConts = subst(Conts, Vars),
401
write_file(Dest, NConts),
402
case lists:member(preserve, Opts) of
404
{ok, FileInfo} = file:read_file_info(Src),
405
file:write_file_info(Dest, FileInfo);
411
%% Vars = [{Var, Val}]
412
%% Var = Val = string()
413
%% Substitute all occurrences of %Var% for Val in Str, using the list
414
%% of variables in Vars.
417
subst(Str, Vars, []).
419
subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
420
subst_var([C| Rest], Vars, Result, []);
421
subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
422
subst_var([C| Rest], Vars, Result, []);
423
subst([$%, C| Rest], Vars, Result) when C == $_ ->
424
subst_var([C| Rest], Vars, Result, []);
425
subst([C| Rest], Vars, Result) ->
426
subst(Rest, Vars, [C| Result]);
427
subst([], _Vars, Result) ->
428
lists:reverse(Result).
430
subst_var([$%| Rest], Vars, Result, VarAcc) ->
431
Key = lists:reverse(VarAcc),
432
case lists:keysearch(Key, 1, Vars) of
433
{value, {Key, Value}} ->
434
subst(Rest, Vars, lists:reverse(Value, Result));
436
subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
438
subst_var([C| Rest], Vars, Result, VarAcc) ->
439
subst_var(Rest, Vars, Result, [C| VarAcc]);
440
subst_var([], Vars, Result, VarAcc) ->
441
subst([], Vars, [VarAcc ++ [$%| Result]]).
443
copy_file(Src, Dest) ->
444
copy_file(Src, Dest, []).
446
copy_file(Src, Dest, Opts) ->
447
{ok, InFd} = file:open(Src, [raw, binary, read]),
448
{ok, OutFd} = file:open(Dest, [raw, binary, write]),
449
do_copy_file(InFd, OutFd),
452
case lists:member(preserve, Opts) of
454
{ok, FileInfo} = file:read_file_info(Src),
455
file:write_file_info(Dest, FileInfo);
460
do_copy_file(InFd, OutFd) ->
461
case file:read(InFd, ?BUFSIZE) of
463
file:write(OutFd, Bin),
464
do_copy_file(InFd, OutFd);
469
write_file(FName, Conts) ->
470
{ok, Fd} = file:open(FName, [write]),
471
file:write(Fd, Conts),
474
read_txt_file(File) ->
475
{ok, Bin} = file:read_file(File),
476
{ok, binary_to_list(Bin)}.
478
remove_dir_tree(Dir) ->
479
remove_all_files(".", [Dir]).
481
remove_all_files(Dir, Files) ->
482
lists:foreach(fun(File) ->
483
FilePath = filename:join([Dir, File]),
484
{ok, FileInfo} = file:read_file_info(FilePath),
485
case FileInfo#file_info.type of
487
{ok, DirFiles} = file:list_dir(FilePath),
488
remove_all_files(FilePath, DirFiles),
489
file:del_dir(FilePath);
491
file:delete(FilePath)