1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%module
21-module(target_system).
22-export([create/1, create/2, install/2]).
23
24%% Note: RelFileName below is the *stem* without trailing .rel,
25%% .script etc.
26%%
27
28%% create(RelFileName)
29%%
30create(RelFileName) ->
31    create(RelFileName,[]).
32
33create(RelFileName,SystoolsOpts) ->
34    RelFile = RelFileName ++ ".rel",
35    Dir = filename:dirname(RelFileName),
36    PlainRelFileName = filename:join(Dir,"plain"),
37    PlainRelFile = PlainRelFileName ++ ".rel",
38    io:fwrite("Reading file: ~tp ...~n", [RelFile]),
39    {ok, [RelSpec]} = file:consult(RelFile),
40    io:fwrite("Creating file: ~tp from ~tp ...~n",
41              [PlainRelFile, RelFile]),
42    {release,
43     {RelName, RelVsn},
44     {erts, ErtsVsn},
45     AppVsns} = RelSpec,
46    PlainRelSpec = {release,
47                    {RelName, RelVsn},
48                    {erts, ErtsVsn},
49                    lists:filter(fun({kernel, _}) ->
50                                         true;
51                                    ({stdlib, _}) ->
52                                         true;
53                                    (_) ->
54                                         false
55                                 end, AppVsns)
56                   },
57    {ok, Fd} = file:open(PlainRelFile, [write]),
58    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
59    file:close(Fd),
60
61    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
62	      [PlainRelFileName,PlainRelFileName]),
63    make_script(PlainRelFileName,SystoolsOpts),
64
65    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
66              [RelFileName, RelFileName]),
67    make_script(RelFileName,SystoolsOpts),
68
69    TarFileName = RelFileName ++ ".tar.gz",
70    io:fwrite("Creating tar file ~tp ...~n", [TarFileName]),
71    make_tar(RelFileName,SystoolsOpts),
72
73    TmpDir = filename:join(Dir,"tmp"),
74    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
75    file:make_dir(TmpDir),
76
77    io:fwrite("Extracting ~tp into directory ~tp ...~n", [TarFileName,TmpDir]),
78    extract_tar(TarFileName, TmpDir),
79
80    TmpBinDir = filename:join([TmpDir, "bin"]),
81    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
82    io:fwrite("Deleting \"erl\" and \"start\" in directory ~tp ...~n",
83              [ErtsBinDir]),
84    file:delete(filename:join([ErtsBinDir, "erl"])),
85    file:delete(filename:join([ErtsBinDir, "start"])),
86
87    io:fwrite("Creating temporary directory ~tp ...~n", [TmpBinDir]),
88    file:make_dir(TmpBinDir),
89
90    io:fwrite("Copying file \"~ts.boot\" to ~tp ...~n",
91              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
92    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
93
94    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
95              "~tp to ~tp ...~n",
96              [ErtsBinDir, TmpBinDir]),
97    copy_file(filename:join([ErtsBinDir, "epmd"]),
98              filename:join([TmpBinDir, "epmd"]), [preserve]),
99    copy_file(filename:join([ErtsBinDir, "run_erl"]),
100              filename:join([TmpBinDir, "run_erl"]), [preserve]),
101    copy_file(filename:join([ErtsBinDir, "to_erl"]),
102              filename:join([TmpBinDir, "to_erl"]), [preserve]),
103
104    %% This is needed if 'start' script created from 'start.src' shall
105    %% be used as it points out this directory as log dir for 'run_erl'
106    TmpLogDir = filename:join([TmpDir, "log"]),
107    io:fwrite("Creating temporary directory ~tp ...~n", [TmpLogDir]),
108    ok = file:make_dir(TmpLogDir),
109
110    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
111    io:fwrite("Creating ~tp ...~n", [StartErlDataFile]),
112    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
113    write_file(StartErlDataFile, StartErlData),
114
115    io:fwrite("Recreating tar file ~tp from contents in directory ~tp ...~n",
116	      [TarFileName,TmpDir]),
117    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
118    %% {ok, Cwd} = file:get_cwd(),
119    %% file:set_cwd("tmp"),
120    ErtsDir = "erts-"++ErtsVsn,
121    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
122    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
123    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
124    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
125    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
126    erl_tar:close(Tar),
127    %% file:set_cwd(Cwd),
128    io:fwrite("Removing directory ~tp ...~n",[TmpDir]),
129    remove_dir_tree(TmpDir),
130    ok.
131
132
133install(RelFileName, RootDir) ->
134    TarFile = RelFileName ++ ".tar.gz",
135    io:fwrite("Extracting ~tp ...~n", [TarFile]),
136    extract_tar(TarFile, RootDir),
137    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
138    {ok, StartErlData} = read_txt_file(StartErlDataFile),
139    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
140    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
141    BinDir = filename:join([RootDir, "bin"]),
142    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
143              "form erl, start and start_erl ...\n"),
144    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
145                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
146                      [preserve]),
147    %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
148    %%! not have correct permissions, so the above 'preserve' option did not help
149    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
150    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
151
152    io:fwrite("Creating the RELEASES file ...\n"),
153    create_RELEASES(RootDir, filename:join([RootDir, "releases",
154					    filename:basename(RelFileName)])).
155
156%% LOCALS
157
158%% make_script(RelFileName,Opts)
159%%
160make_script(RelFileName,Opts) ->
161    systools:make_script(RelFileName, [no_module_tests,
162				       {outdir,filename:dirname(RelFileName)}
163				       |Opts]).
164
165%% make_tar(RelFileName,Opts)
166%%
167make_tar(RelFileName,Opts) ->
168    RootDir = code:root_dir(),
169    systools:make_tar(RelFileName, [{erts, RootDir},
170				    {outdir,filename:dirname(RelFileName)}
171				    |Opts]).
172
173%% extract_tar(TarFile, DestDir)
174%%
175extract_tar(TarFile, DestDir) ->
176    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
177
178create_RELEASES(DestDir, RelFileName) ->
179    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
180
181subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
182    lists:foreach(fun(Script) ->
183                          subst_src_script(Script, SrcDir, DestDir,
184                                           Vars, Opts)
185                  end, Scripts).
186
187subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
188    subst_file(filename:join([SrcDir, Script ++ ".src"]),
189               filename:join([DestDir, Script]),
190               Vars, Opts).
191
192subst_file(Src, Dest, Vars, Opts) ->
193    {ok, Conts} = read_txt_file(Src),
194    NConts = subst(Conts, Vars),
195    write_file(Dest, NConts),
196    case lists:member(preserve, Opts) of
197        true ->
198            {ok, FileInfo} = file:read_file_info(Src),
199            file:write_file_info(Dest, FileInfo);
200        false ->
201            ok
202    end.
203
204%% subst(Str, Vars)
205%% Vars = [{Var, Val}]
206%% Var = Val = string()
207%% Substitute all occurrences of %Var% for Val in Str, using the list
208%% of variables in Vars.
209%%
210subst(Str, Vars) ->
211    subst(Str, Vars, []).
212
213subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
214    subst_var([C| Rest], Vars, Result, []);
215subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
216    subst_var([C| Rest], Vars, Result, []);
217subst([$%, C| Rest], Vars, Result) when  C == $_ ->
218    subst_var([C| Rest], Vars, Result, []);
219subst([C| Rest], Vars, Result) ->
220    subst(Rest, Vars, [C| Result]);
221subst([], _Vars, Result) ->
222    lists:reverse(Result).
223
224subst_var([$%| Rest], Vars, Result, VarAcc) ->
225    Key = lists:reverse(VarAcc),
226    case lists:keysearch(Key, 1, Vars) of
227        {value, {Key, Value}} ->
228            subst(Rest, Vars, lists:reverse(Value, Result));
229        false ->
230            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
231    end;
232subst_var([C| Rest], Vars, Result, VarAcc) ->
233    subst_var(Rest, Vars, Result, [C| VarAcc]);
234subst_var([], Vars, Result, VarAcc) ->
235    subst([], Vars, [VarAcc ++ [$%| Result]]).
236
237copy_file(Src, Dest) ->
238    copy_file(Src, Dest, []).
239
240copy_file(Src, Dest, Opts) ->
241    {ok,_} = file:copy(Src, Dest),
242    case lists:member(preserve, Opts) of
243        true ->
244            {ok, FileInfo} = file:read_file_info(Src),
245            file:write_file_info(Dest, FileInfo);
246        false ->
247            ok
248    end.
249
250write_file(FName, Conts) ->
251    Enc = file:native_name_encoding(),
252    {ok, Fd} = file:open(FName, [write]),
253    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
254    file:close(Fd).
255
256read_txt_file(File) ->
257    {ok, Bin} = file:read_file(File),
258    {ok, binary_to_list(Bin)}.
259
260remove_dir_tree(Dir) ->
261    remove_all_files(".", [Dir]).
262
263remove_all_files(Dir, Files) ->
264    lists:foreach(fun(File) ->
265                          FilePath = filename:join([Dir, File]),
266                          case filelib:is_dir(FilePath) of
267                              true ->
268                                  {ok, DirFiles} = file:list_dir(FilePath),
269                                  remove_all_files(FilePath, DirFiles),
270                                  file:del_dir(FilePath);
271                              _ ->
272                                  file:delete(FilePath)
273                          end
274                  end, Files).
275%module
276