1 %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
2 %%%---------------------------------------------------------------------------
3 %%% @author Eric Merritt
5 %%% Builds the *.script and *.boot from the project rel file.
7 %%% @copyright (C) 2006-2011 Erlware
8 %%%---------------------------------------------------------------------------
9 -module(sin_task_release
).
13 -include_lib("sinan/include/sinan.hrl").
16 -export([description
/0, do_task
/2, format_exception
/1]).
18 -define(TASK
, release
).
19 -define(DEPS
, [build
]).
21 %%====================================================================
23 %%====================================================================
25 %% @doc describes this task to the system
26 -spec
description() -> sin_task:task_description().
33 This command creates all the artifacts needed to start the current
34 project as an otp release. This creates the `*.rel`, `*.boot` and `*.script`
35 files into the output area of the project.
37 Those files maybe found at: `<build-area>/<release-name>/releases/<release-vsn>`
39 To understand what those files are and why they are important you should check
40 the erlang documentation on releases.
45 ### Application Load Types
47 This configuration allows you to specify the load type for the application
48 specified in `AppName` for the release. (Again review the release information
51 {types, [{AppName1::atom(), RelType::atom()},
52 {AppName2::atom(), RelType::atom()}]}.
56 You may also add additional options to the sys_tools:make_script
57 call. The one you probably want to add most is the local option, but you
58 may add any valid option. You do this by adding the following call to
61 {script_args, [term()]}.
63 Not that the value for script_args must always be a list
65 ### Additional Release Dirs
67 By default the release only includes directories directly relevant to the erlang
68 otp release. It is common to want to include additional directories. You can do
69 that with the following configuration.
71 {include_dirs, [string()]}.
73 ### Including The Erts Dir
75 This is a boolean that indicates to the system whether or not you want the
76 Erlang runtime system included in the tarball. This allows you to distribute
77 the vm with your release but has the drawback of turning your tarball into a
78 platform specific thing.
80 {include_erts, boolean()}.",
87 short_desc
= "Creates an otp release for the system",
91 %% @doc create an otp release
92 -spec
do_task(sin_config:matcher(), sin_state:state()) -> sin_state:state().
93 do_task(Config
, State0
) ->
94 BuildDir
= sin_state:get_value(build_dir
, State0
),
95 ReleaseDir
= sin_state:get_value(release_dir
, State0
),
96 ReleaseName
= sin_state:get_value(release
, State0
),
97 Version
= sin_state:get_value(release_vsn
, State0
),
98 ReleaseInfo
= generate_rel_file(Config
, State0
, ReleaseDir
,
99 ReleaseName
, Version
),
100 State1
= sin_state:store(rel
, ReleaseInfo
, State0
),
101 copy_include_dirs(Config
, State1
, BuildDir
),
102 copy_apps(Config
, State1
),
103 create_bin_file(State1
, BuildDir
,
104 ReleaseName
, Version
, get_erts_info()),
105 copy_or_generate_sys_config_file(Config
, ReleaseDir
, Version
),
106 include_erts(Config
, State1
, BuildDir
),
107 make_boot_script(State1
, Config
, ReleaseInfo
),
108 launch_if_required(Config
, BuildDir
, ReleaseName
, Version
),
111 %% @doc Format an exception thrown by this module
112 -spec
format_exception(sin_exceptions:exception()) ->
114 format_exception(?
SIN_EXEP_UNPARSE(_
, release_script_generation_error
, Description
)) ->
116 format_exception(Exception
) ->
117 sin_exceptions:format_exception(Exception
).
119 %%====================================================================
120 %% Internal functions
121 %%====================================================================
123 %% @doc Generate release information from info available in the project.
124 -spec
generate_rel_file(sin_config:config(), sin_state:state(), string(),
125 string(), string()) ->
126 {ReleaseFile::string(), ReleaseInfo::term()}.
127 generate_rel_file(Config
, State
, ReleaseDir
, Name
, Version
) ->
128 ReleaseName
= sin_state:get_value(release
, State
),
129 ReleaseVsn
= sin_state:get_value(release_vsn
, State
),
131 RootDir
= sin_state:get_value(project_dir
, State
),
134 case sin_release:get_release(State
, RootDir
, ReleaseName
,
137 Erts
= get_erts_info(),
139 process_deps(Config:match(types
, []),
140 sin_state:get_value(release_runtime_deps
, State
) ++
141 sin_state:get_value(project_apps
, State
), []),
142 Deps1
= lists:map(fun({App
, AppVersion
}) ->
146 {release
, {erlang:atom_to_list(Name
), Version
}, {erts
, Erts
},
151 {save_release(Config
, State
, ReleaseDir
, Name
, Version
, Release
), Release
}.
153 %% @doc Process the dependencies into a format useful for the rel depends area.
154 process_deps(Types
, [#app
{name
=App
, vsn
=Vsn
} | T
], Acc
) ->
155 case lists:keyfind(App
, 1, Types
) of
157 process_deps(Types
, T
, [{App
, Vsn
, list_to_atom(Type
)} | Acc
]);
159 process_deps(Types
, T
, [{App
, Vsn
} | Acc
])
161 process_deps(_
, [], Acc
) ->
164 %% @doc Save the release terms to a releases file for later use by the system.
165 -spec
save_release(sin_config:config(), sin_state:state(), string(),
166 string(), string(), term()) ->
167 {Location::string(), RelBase::string()}.
168 save_release(Config
, State
, RelDir
, Name
, Version
, RelInfo
) ->
169 Location
= filename:join(RelDir
, Version
),
170 filelib:ensure_dir(filename:join([Location
, "tmp"])),
171 Relbase
= filename:join([Location
, Name
]),
172 Relf
= lists:flatten([Relbase
, ".rel"]),
173 case file:open(Relf
, write
) of
175 sin_log:normal(Config
, "Couldn't open ~s for writing. Unable to "
176 "write release information",
179 unable_to_write_rel_info
);
181 io:format(IoDev
, "~p.", [RelInfo
]),
186 %% @doc Get the system erts version.
187 -spec
get_erts_info() -> ErtsVersion::string().
189 erlang:system_info(version
).
191 %% @doc Gather up the path information and make the boot/script files.
192 -spec
make_boot_script(sin_state:state(), sin_config:config(), {{string(), string()}, term()}) ->
194 make_boot_script(State
, Config
, {{Location
, File
}, {release
, {Name
, _
}, _
, _
}}) ->
195 Options
= [{path
, [Location
| get_code_paths(State
)]},
196 no_module_tests
, silent
] ++ Config:match(script_args
, []),
197 case make_script(Name
, File
, Location
, Options
) of
201 ?
SIN_RAISE(State
, release_script_generation_error
);
204 {ok
,Module
,Warnings
} ->
205 Detail
= lists:flatten(Module:format_warning(Warnings
)),
206 ?
SIN_RAISE(State
, release_script_generation_error
,
208 {error
,Module
,Error
} ->
209 Detail
= lists:flatten(Module:format_error(Error
)),
210 ?
SIN_RAISE(State
, release_script_generation_error
,
214 -spec
make_script(string(), string(), string(), [term()]) ->
215 ok
| error
| {ok
, term(), []} |
216 {ok
, atom(), [term()]} |
217 {ok
, atom(), [term()]}.
218 make_script(Name
, File
, Location
, Options
) ->
219 %% Erts 5.9 introduced a non backwards compatible option to
220 %% erlang this takes that into account
221 Erts
= erlang:system_info(version
),
222 case Erts
== "5.9" orelse
ec_string:compare_versions(Erts
, "5.9") of
224 systools_make:make_script(Name
,
226 {outdir
, Location
} | Options
]);
228 systools_make:make_script(Name
,
229 File
, [{outdir
, Location
} | Options
])
232 %% @doc copy config/sys.config or generate one to releases/VSN/sys.config
233 -spec
copy_or_generate_sys_config_file(sin_config:config(), string(),
236 copy_or_generate_sys_config_file(Config
, RelDir
, Version
) ->
237 RelSysConfPath
= filename:join([RelDir
, Version
, "sys.config"]),
238 case Config:match(config_dir
, undefined
) of
240 generate_sys_config_file(RelSysConfPath
);
242 ConfSysConfPath
= filename:join([ConfigDir
, "sys.config"]),
243 case filelib:is_regular(ConfSysConfPath
) of
245 generate_sys_config_file(RelSysConfPath
);
247 file:copy(ConfSysConfPath
, RelSysConfPath
)
251 %% @doc write a generic sys.config to the path RelSysConfPath
252 -spec
generate_sys_config_file(string()) -> ok
.
253 generate_sys_config_file(RelSysConfPath
) ->
254 {ok
, Fd
} = file:open(RelSysConfPath
, [write
]),
256 "%% Thanks to Ulf Wiger at Ericcson for these comments:~n"
258 "%% This file is identified via the erl command line option -config File.~n"
259 "%% Note that File should have no extension, e.g.~n"
260 "%% erl -config .../sys (if this file is called sys.config)~n"
262 "%% In this file, you can redefine application environment variables.~n"
263 "%% This way, you don't have to modify the .app files of e.g. OTP applications.~n"
269 %% @doc Generates the correct set of code paths for the system.
270 -spec
get_code_paths(sin_state:state()) -> sin_config:config().
271 get_code_paths(State
) ->
272 [filename:join([Path
, "ebin"]) ||
274 sin_state:get_value(release_runtime_deps
, State
) ++
275 sin_state:get_value(project_apps
, State
)].
277 %% @doc Optionally add erts directory to release, if defined.
278 -spec
include_erts(sin_config:config(), sin_state:state(), string()) -> ok
.
279 include_erts(Config
, State
, ReleaseRootDir
) ->
280 case Config:match(include_erts
, false
) of
282 ErtsDir
= sin_utils:get_erts_dir(),
283 sin_utils:copy_dir(Config
, State
, ReleaseRootDir
, ErtsDir
, [keep_parent
]);
288 copy_apps(Config
, State
) ->
289 LibDir
= sin_state:get_value(apps_dir
, State
),
290 Apps
= sin_state:get_value(release_runtime_deps
, State
),
291 lists:foreach(fun(#app
{path
=Path
}) ->
292 %% Don't copy it if it already exists in the libdir
293 AppName
= filename:basename(Path
),
294 case sin_utils:file_exists(State
, filename:join(LibDir
, AppName
)) of
296 sin_utils:copy_dir(Config
, State
, LibDir
, Path
, [keep_parent
]);
302 copy_include_dirs(Config
, State
, BuildDir
) ->
303 case Config:match(include_dirs
, undefined
) of
307 lists:foreach(fun(File
) ->
308 case sin_utils:file_exists(State
, File
) of
310 sin_utils:copy_dir(Config
, State
, BuildDir
,
311 File
, [keep_parent
]);
315 %% Bin is always included by default
316 end, ["bin" | RequiredDirs
])
319 create_bin_file(State
, BuildDir
, RelName
, RelVsn
, ErtsVsn
) ->
320 BinDir
= filename:join([BuildDir
, "bin"]),
321 filelib:ensure_dir(filename:join(BinDir
, "tmp")),
322 VsnRel
= filename:join(BinDir
, erlang:atom_to_list(RelName
) ++ "-" ++ RelVsn
),
323 BareRel
= filename:join(BinDir
, erlang:atom_to_list(RelName
)),
324 StartFile
= bin_file_contents(RelName
, RelVsn
, ErtsVsn
),
325 case sin_utils:file_exists(State
, VsnRel
) of
327 ok
= file:write_file(VsnRel
, StartFile
),
328 ok
= file:change_mode(VsnRel
, 8#
777);
333 case sin_utils:file_exists(State
, BareRel
) of
335 ok
= file:write_file(BareRel
, StartFile
),
336 ok
= file:change_mode(BareRel
, 8#
777);
342 launch_if_required(Config
, BuildDir
, RelName
, RelVsn
) ->
343 BinDir
= filename:join([BuildDir
, "bin"]),
344 VsnRel
= filename:join(BinDir
, erlang:atom_to_list(RelName
) ++ "-" ++ RelVsn
),
345 AdditionalArgs
= Config:match(additional_args
),
346 case AdditionalArgs
of
354 Port
= open_port({spawn, Cmd
}, [ exit_status
, binary ] ),
359 {Port
, {data
, BinData
}} ->
360 io:format("~s~n", [BinData
]),
362 {Port
, {exit_status
, Status
}} ->
363 io:format("exit_code: ~p~n", [Status
]);
368 bin_file_contents(RelName
, RelVsn
, ErtsVsn
) ->
374 SCRIPT_DIR=`dirname $0`
375 RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd`
376 REL_NAME=">>, erlang:atom_to_list(RelName
), <<"
377 REL_VSN=">>, RelVsn
, <<"
378 ERTS_VSN=">>, ErtsVsn
, <<"
384 local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN
385 if [ -d \"$erts_dir\" ]; then
387 ROOTDIR=$RELEASE_ROOT_DIR
389 local erl=`which erl`
390 local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop`
391 ERTS_DIR=$erl_root/erts-$ERTS_VSN
398 local possible_sys=$RELEASE_ROOT_DIR/releases/$REL_VSN/sys.config
399 if [ -f \"$possible_sys\" ]; then
400 SYS_CONFIG=\"-config $possible_sys\"
407 export ROOTDIR=$RELEASE_ROOT_DIR
408 export BINDIR=$ERTS_DIR/bin
411 export LD_LIBRARY_PATH=$ERTS_DIR/lib
413 REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_VSN
415 $BINDIR/erlexec $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@