1 %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
2 %%%---------------------------------------------------------------------------
3 %%% @author Eric Merritt
5 %%% Builds up a escript distributable application (in the sense of a unix application,
6 %%% not an otp application). It looks for an escript directive
7 %%% all of the apps to the system.
9 %%% @copyright (C) 2007-2011 Erlware
10 %%%---------------------------------------------------------------------------
11 -module(sin_task_escript
).
15 -include_lib("sinan/include/sinan.hrl").
18 -export([description
/0, do_task
/2, format_exception
/1]).
20 -define(TASK
, escript
).
21 -define(DEPS
, [build
]).
23 %%====================================================================
25 %%====================================================================
27 %% @doc provides a description of the sytem, for help and other reasons
28 -spec
description() -> sin_task:task_description().
35 This takes the current project and turns it into an executable
36 [Escript](http://www.erlang.org/doc/man/escript.html).
38 Be aware though that there are significant limitations in escript. These are not
39 limitations of Sinan, but limitations in the built in escript
40 functionality. Your escript may be built off of a single script OR Erlang OTP
41 Applications but not both The system will warn you if you violate these
47 The escript task allows for a few options in the sinan.config file. The
48 options are specified as follows:
50 {escript, [{Key::atom()), term()}]}.
52 The keys available are:
54 {source, Path::string()}
56 The source command is used if you want to include a single source file as the
59 {emu_args, Args::string()}.
61 {include_apps, [App::term()]}.
63 include_apps is a list of all the apps that are not part of the project that you
64 want to include in the escript. The project apps are all included by default.
66 So a fully configured escript config might look like:
69 [{source, \"bin/my_cool_escript_file\"},
70 {emu_args, \"-smp disable\"},
71 {include_apps, [kernel, stdlib, my_dep]}.
73 See the escript documentation for details and remember to only pass the
74 script option if you want that to be your escript.",
81 short_desc
= "Provides a standard erlang escript",
85 %% @doc Build an escript for this project
86 -spec
do_task(sin_config:matcher(), sin_state:state()) -> sin_state:state().
87 do_task(Config
, State
) ->
88 ProjectDir
= sin_state:get_value(project_dir
, State
),
89 ReleaseApps
= sin_state:get_value(release_apps
, State
),
90 ProjectApps
= sin_state:get_value(project_apps
, State
),
91 BuildDir
= sin_state:get_value(build_dir
, State
),
92 EscriptDir
= filename:join([BuildDir
, "escript"]),
93 EscriptWorkingDir
= filename:join(EscriptDir
, ".ez"),
94 ec_file:mkdir_path(EscriptWorkingDir
),
95 ReleaseName
= sin_state:get_value(release
, State
),
97 EscriptOptions
= Config:match(escript
, []),
98 PossibleSourceFile
= get_source_file(Config
, State
, ProjectDir
, EscriptOptions
),
101 case PossibleSourceFile
of
103 make_archive(Config
, State
,
106 gather_dirs(Config
, State
, EscriptWorkingDir
,
107 filter_apps(ReleaseApps
,
112 sin_log:normal(Config
, "With escript you may have source files "
113 "or archive files, but you may not have "
114 "both. Since script files are defined "
115 "I am omiting dependency archives"),
119 EscriptTarget
= filename:join([EscriptDir
, ReleaseName
]),
122 case lists:keyfind(emu_args
, 1, EscriptOptions
) of
123 {emu_args
, ArgList
} when is_list(ArgList
) ->
127 {emu_args
, BadArgs
} ->
128 sin_log:normal(Config
, "emu_args to escript must be a list! not ~p",
130 ?
SIN_RAISE(State
, {bad_emu_args
, BadArgs
})
133 case escript:create(EscriptTarget
,
134 lists:flatten([shebang
, EmuArgs
,
135 PossibleSourceFile
, Body
])) of
137 sin_log:normal(Config
, "Escript created at ~s",
139 Error
= {error
, _
} ->
140 sin_log:normal(Config
, "Enable to create escript at ~s due to ~p!",
141 [EscriptTarget
, Error
]),
142 ?
SIN_RAISE(State
, {unable_to_create_escript
, Error
})
144 %% Execute owner 8#00100, Execute group 8#00010, Execute other 8#00001
145 %% Read owner 8#00400, Read group 8#00040, Read other 8#00004
146 %% Write owner 8#00200, Write group 8#00020, Write other 8#00002
147 %% The mode is the sum of permissions
148 file:change_mode(EscriptTarget
, 8#
00100 + 8#
00010 + 8#
00001 +
149 8#
00400 + 8#
00040 + 8#
00004 +
150 8#
00200 + 8#
00020 + 8#
00002),
151 sin_utils:delete_dir(EscriptWorkingDir
),
154 %% @doc Format an exception thrown by this module
155 -spec
format_exception(sin_exceptions:exception()) ->
157 format_exception(Exception
) ->
158 sin_exceptions:format_exception(Exception
).
160 %%====================================================================
161 %%% Internal functions
162 %%====================================================================
164 -spec
gather_dirs(sin_config:config(),
165 sin_state:state(), string(), [tuple()], [string()]) ->
167 gather_dirs(Config
, State0
, EscriptTargetDir
,
168 [#app
{name
=AppName
, vsn
=Vsn
, path
=Path
} | T
], FileList
) ->
169 FileName
= erlang:atom_to_list(AppName
) ++ "-" ++ Vsn
,
170 Target
= filename:join(EscriptTargetDir
, FileName
),
171 ok
= ec_file:mkdir_path(Target
),
172 State1
= sin_utils:copy_dir(Config
, State0
, Target
, Path
),
173 gather_dirs(Config
, State1
, EscriptTargetDir
, T
, [FileName
| FileList
]);
174 gather_dirs(_
, _State
, _
, [], FileList
) ->
177 make_archive(Config
, State
, ProjectName
, CWD
, FileList
) ->
178 EscriptPath
= filename:join([CWD
,
179 erlang:atom_to_list(ProjectName
) ++ ".ez"]),
180 case zip:create(EscriptPath
,
184 {uncompress
,[".beam",".app"]}]) of
188 sin_log:normal(Config
, "Error trying to write ez archive "
189 "for applications in ~s. This is "
190 "probably due to dot files (.* .#* "
191 "like emacs archive files in the "
192 "application directory. Do a sinan clean,"
193 "clean out the project directory and "
194 "try again", [EscriptPath
]),
195 ?
SIN_RAISE(State
, {error_creating_archive
, EscriptPath
});
197 sin_log:normal(Config
, "Unknown error (~p) occured while "
198 "trying to write ~s to ~s",
199 [Error
, CWD
, EscriptPath
]),
200 ?
SIN_RAISE(State
, {error_creating_archive
, Error
})
202 {archive
, EscriptPath
}.
205 get_source_file(Config
, State
, ProjectDir
, EscriptOptions
) ->
207 lists:foldl(fun({source
, SourceFile
}, Acc
) ->
208 AbsoluteSourcePath
= filename:join(ProjectDir
,
210 case file:read_file(AbsoluteSourcePath
) of
212 [{source
, Source
} | Acc
];
213 Error
= {error
, _
} ->
215 {unable_to_read_source
,
216 AbsoluteSourcePath
, Error
})
220 end, [], EscriptOptions
),
227 sin_log:normal(Config
, "You may only have one source entry in your "
228 "escript directive"),
229 ?
SIN_RAISE(State
, {multiple_source_entries
, Sources
})
232 filter_apps(Apps
, EscriptOptions
) ->
234 case lists:keyfind(include_apps
, 1, EscriptOptions
) of
235 {include_apps
, IApps
} ->
240 lists:filter(fun(#app
{name
=AppName
}) ->
241 lists:member(AppName
, IncludedApps
)