Skip to content

Commit 0a92931

Browse files
committed
Support building Erlang-based escripts in mix escript.build
1 parent ebd18d4 commit 0a92931

File tree

4 files changed

+128
-44
lines changed

4 files changed

+128
-44
lines changed

lib/mix/lib/mix/tasks/escript.build.ex

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule Mix.Tasks.Escript.Build do
4545
* `:embed_elixir` - if `true` embed elixir and its children apps
4646
(`ex_unit`, `mix`, etc.) mentioned in the `:applications` list inside the
4747
`application` function in `mix.exs`.
48-
Defaults to `true`.
48+
Defaults to `true` for Elixir projects.
4949
5050
* `:shebang` - shebang interpreter directive used to execute the escript.
5151
Defaults to `"#! /usr/bin/env escript\n"`.
@@ -90,6 +90,7 @@ defmodule Mix.Tasks.Escript.Build do
9090
main = escript_opts[:main_module]
9191
app = Keyword.get(escript_opts, :app, project[:app])
9292
files = project_files()
93+
language = Keyword.get(project, :language, :elixir)
9394

9495
escript_mod = String.to_atom(Atom.to_string(app) <> "_escript")
9596

@@ -103,8 +104,9 @@ defmodule Mix.Tasks.Escript.Build do
103104
"in your project configuration (under `:escript` option) to a module that implements main/1"
104105

105106
force || Mix.Utils.stale?(files, [filename]) ->
106-
tuples = gen_main(escript_mod, main, app) ++
107-
to_tuples(files) ++ deps_tuples() ++ embed_tuples(escript_opts)
107+
tuples = gen_main(escript_mod, main, app, language) ++
108+
to_tuples(files) ++ deps_tuples() ++
109+
embed_tuples(escript_opts, language)
108110

109111
case :zip.create 'mem', tuples, [:memory] do
110112
{:ok, {'mem', zip}} ->
@@ -156,8 +158,8 @@ defmodule Mix.Tasks.Escript.Build do
156158
Enum.flat_map(deps, fn dep -> get_tuples(dep.opts[:build]) end)
157159
end
158160

159-
defp embed_tuples(escript_opts) do
160-
if Keyword.get(escript_opts, :embed_elixir, true) do
161+
defp embed_tuples(escript_opts, language) do
162+
if Keyword.get(escript_opts, :embed_elixir, language == :elixir) do
161163
Enum.flat_map [:elixir|extra_apps()], &app_tuples(&1)
162164
else
163165
[]
@@ -190,61 +192,104 @@ defmodule Mix.Tasks.Escript.Build do
190192
"%%! -escript main #{escript_mod} #{user_args}\n"
191193
end
192194

193-
defp gen_main(name, module, app) do
195+
defp gen_main(name, module, app, language) do
194196
config =
195197
if File.regular?("config/config.exs") do
196198
Mix.Config.read!("config/config.exs")
197199
else
198200
[]
199201
end
200202

201-
{:module, ^name, binary, _} =
202-
defmodule name do
203-
@module module
204-
@config config
205-
@app app
206-
207-
# We need to use Erlang modules at this point
208-
# because we are not sure Elixir is available.
209-
def main(args) do
210-
case :application.ensure_all_started(:elixir) do
211-
{:ok, _} ->
212-
load_config(@config)
213-
start_app(@app)
214-
args = Enum.map(args, &List.to_string(&1))
215-
Kernel.CLI.run fn _ -> @module.main(args) end, true
216-
_ ->
217-
:io.put_chars :standard_error, "Elixir is not available, aborting.\n"
218-
:erlang.halt(1)
219-
end
220-
end
203+
module_body = quote do
204+
@module unquote(module)
205+
@config unquote(config)
206+
@app unquote(app)
221207

222-
defp load_config(config) do
223-
for {app, kw} <- config, {k, v} <- kw do
224-
:application.set_env(app, k, v, persistent: true)
225-
end
226-
:ok
208+
# We need to use Erlang modules at this point
209+
# because we are not sure Elixir is available.
210+
unquote(module_body_for(language))
211+
end
212+
213+
{:module, ^name, binary, _} = Module.create(name, module_body, Macro.Env.location(__ENV__))
214+
[{'#{name}.beam', binary}]
215+
end
216+
217+
defp module_body_for(:elixir) do
218+
quote do
219+
def main(args) do
220+
case :application.ensure_all_started(:elixir) do
221+
{:ok, _} ->
222+
load_config(@config)
223+
start_app(@app)
224+
args = Enum.map(args, &List.to_string(&1))
225+
Kernel.CLI.run fn _ -> @module.main(args) end, true
226+
_ ->
227+
:io.put_chars :standard_error, "Elixir is not available, aborting.\n"
228+
:erlang.halt(1)
227229
end
230+
end
228231

229-
defp start_app(nil) do
230-
:ok
232+
defp load_config(config) do
233+
for {app, kw} <- config, {k, v} <- kw do
234+
:application.set_env(app, k, v, persistent: true)
231235
end
236+
:ok
237+
end
232238

233-
defp start_app(app) do
234-
case :application.ensure_all_started(app) do
235-
{:ok, _} -> :ok
236-
{:error, {app, reason}} ->
237-
io_error "Could not start application #{app}: " <>
238-
Application.format_error(reason)
239-
System.halt(1)
240-
end
239+
defp start_app(nil) do
240+
:ok
241+
end
242+
243+
defp start_app(app) do
244+
case :application.ensure_all_started(app) do
245+
{:ok, _} -> :ok
246+
{:error, {app, reason}} ->
247+
io_error "Could not start application #{app}: " <>
248+
Application.format_error(reason)
249+
System.halt(1)
241250
end
251+
end
252+
253+
defp io_error(message) do
254+
IO.puts :stderr, IO.ANSI.format([:red, :bright, message])
255+
end
256+
end
257+
end
258+
259+
defp module_body_for(:erlang) do
260+
quote do
261+
def main(args) do
262+
load_config(@config)
263+
start_app(@app)
264+
@module.main(args)
265+
end
242266

243-
defp io_error(message) do
244-
IO.puts :stderr, IO.ANSI.format([:red, :bright, message])
267+
defp load_config(config) do
268+
:lists.foreach(fn {app, kw} ->
269+
:lists.foreach(fn {k, v} ->
270+
:application.set_env(app, k, v, persistent: true)
271+
end, kw)
272+
end, config)
273+
:ok
274+
end
275+
276+
defp start_app(nil) do
277+
:ok
278+
end
279+
280+
defp start_app(app) do
281+
case :application.ensure_all_started(app) do
282+
{:ok, _} -> :ok
283+
{:error, {app, reason}} ->
284+
io_error ["Could not start application #{app}: ",
285+
:io_lib.format('~p~n', [reason])]
286+
:erlang.halt(1)
245287
end
246288
end
247289

248-
[{'#{name}.beam', binary}]
290+
defp io_error(message) do
291+
:io.put_chars(:standard_error, message)
292+
end
293+
end
249294
end
250295
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
use Mix.Config
2+
3+
config :escripttest, erlval: "Erlang value"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-module(escripttest).
2+
3+
-export([start/0, main/1]).
4+
5+
6+
start() ->
7+
ok = application:start(escripttest).
8+
9+
main(_Args) ->
10+
{ok, Val} = application:get_env(escripttest, erlval),
11+
io:put_chars(Val).

lib/mix/test/mix/tasks/escript_test.exs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ defmodule Mix.Tasks.EscriptTest do
4242
end
4343
end
4444

45+
defmodule EscriptErlangWithDeps do
46+
def project do
47+
[ app: :escripttesterlangwithdeps,
48+
version: "0.0.1",
49+
language: :erlang,
50+
escript: [
51+
main_module: :escripttest,
52+
path: Path.join("ebin", "escripttesterlangwithdeps"),
53+
],
54+
deps: [{:ok, path: fixture_path("deps_status/deps/ok")}] ]
55+
end
56+
end
57+
4558
test "generate escript" do
4659
Mix.Project.push Escript
4760

@@ -90,4 +103,16 @@ defmodule Mix.Tasks.EscriptTest do
90103
after
91104
purge [Ok.Mixfile]
92105
end
106+
107+
test "generate Erlang escript with config and deps" do
108+
Mix.Project.push EscriptErlangWithDeps
109+
110+
in_fixture "escripttest", fn ->
111+
Mix.Tasks.Escript.Build.run []
112+
assert_received {:mix_shell, :info, ["Generated escript ebin/escripttesterlangwithdeps with MIX_ENV=dev"]}
113+
assert System.cmd("escript", ["ebin/escripttesterlangwithdeps"]) == {"Erlang value", 0}
114+
end
115+
after
116+
purge [Ok.Mixfile]
117+
end
93118
end

0 commit comments

Comments
 (0)