Skip to content

Commit 74391b7

Browse files
committed
Merge pull request #2687 from alco/mix-language-setting
Mix language setting
2 parents 3e54b95 + f0b8bc1 commit 74391b7

File tree

6 files changed

+133
-47
lines changed

6 files changed

+133
-47
lines changed

lib/mix/lib/mix/project.ex

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Mix.Project do
1111
1212
def project do
1313
[app: :my_app,
14-
vsn: "0.6.0"]
14+
version: "0.6.0"]
1515
end
1616
end
1717
@@ -24,6 +24,16 @@ defmodule Mix.Project do
2424
function in the project, the developer can call `Mix.Project.get!/0`
2525
which fails with `Mix.NoProjectError` in case a project is not
2626
defined.
27+
28+
## Erlang projects
29+
30+
Mix can be used to manage Erlang projects that don't have any Elixir code. To
31+
ensure mix tasks work correctly for an Erlang project, `language: :erlang`
32+
has to be added to `project`.
33+
34+
The setting also makes sure Elixir is not added as a dependency to the
35+
generated .app file or to the escript generated with `mix escript.build`,
36+
etc.
2737
"""
2838

2939
@doc false

lib/mix/lib/mix/tasks/compile.app.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ defmodule Mix.Tasks.Compile.App do
9090
end
9191

9292
# Ensure we always prepend the standard application dependencies
93+
core_apps = [:kernel, :stdlib] ++ language_app(config)
9394
properties = Keyword.update!(properties, :applications, fn apps ->
94-
[:kernel, :stdlib, :elixir] ++ apps
95+
core_apps ++ apps
9596
end)
9697

9798
properties = ensure_correct_properties(app, config, properties)
@@ -129,6 +130,14 @@ defmodule Mix.Tasks.Compile.App do
129130
Enum.map beams, &(&1 |> Path.basename |> Path.rootname(".beam") |> String.to_atom)
130131
end
131132

133+
defp language_app(config) do
134+
case Keyword.fetch(config, :language) do
135+
{:ok, :elixir} -> [:elixir]
136+
{:ok, :erlang} -> []
137+
:error -> [:elixir]
138+
end
139+
end
140+
132141
defp ensure_correct_properties(app, config, properties) do
133142
properties
134143
|> Keyword.put_new(:description, to_char_list(config[:description] || app))

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

Lines changed: 73 additions & 45 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"`.
@@ -56,6 +56,13 @@ defmodule Mix.Tasks.Escript.Build do
5656
* `:emu_args` - emulator arguments to embed in the escript file.
5757
Defaults to `""`.
5858
59+
There is one project-level option that affects how the escript is generated:
60+
61+
* `language: :elixir | :erlang` - set it to `:erlang` for Erlang projects
62+
managed by mix. Doing so will ensure Elixir is not embedded by default.
63+
Your app will still be started as part of escript loading, with the
64+
config used during build.
65+
5966
## Example
6067
6168
defmodule MyApp.Mixfile do
@@ -90,6 +97,7 @@ defmodule Mix.Tasks.Escript.Build do
9097
main = escript_opts[:main_module]
9198
app = Keyword.get(escript_opts, :app, project[:app])
9299
files = project_files()
100+
language = Keyword.get(project, :language, :elixir)
93101

94102
escript_mod = String.to_atom(Atom.to_string(app) <> "_escript")
95103

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

105113
force || Mix.Utils.stale?(files, [filename]) ->
106-
tuples = gen_main(escript_mod, main, app) ++
107-
to_tuples(files) ++ deps_tuples() ++ embed_tuples(escript_opts)
114+
tuples = gen_main(escript_mod, main, app, language) ++
115+
to_tuples(files) ++ deps_tuples() ++
116+
embed_tuples(escript_opts, language)
108117

109118
case :zip.create 'mem', tuples, [:memory] do
110119
{:ok, {'mem', zip}} ->
@@ -156,8 +165,8 @@ defmodule Mix.Tasks.Escript.Build do
156165
Enum.flat_map(deps, fn dep -> get_tuples(dep.opts[:build]) end)
157166
end
158167

159-
defp embed_tuples(escript_opts) do
160-
if Keyword.get(escript_opts, :embed_elixir, true) do
168+
defp embed_tuples(escript_opts, language) do
169+
if Keyword.get(escript_opts, :embed_elixir, language == :elixir) do
161170
Enum.flat_map [:elixir|extra_apps()], &app_tuples(&1)
162171
else
163172
[]
@@ -190,61 +199,80 @@ defmodule Mix.Tasks.Escript.Build do
190199
"%%! -escript main #{escript_mod} #{user_args}\n"
191200
end
192201

193-
defp gen_main(name, module, app) do
202+
defp gen_main(name, module, app, language) do
194203
config =
195204
if File.regular?("config/config.exs") do
196205
Mix.Config.read!("config/config.exs")
197206
else
198207
[]
199208
end
200209

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
210+
module_body = quote do
211+
@module unquote(module)
212+
@config unquote(config)
213+
@app unquote(app)
221214

222-
defp load_config(config) do
223-
for {app, kw} <- config, {k, v} <- kw do
215+
def main(args) do
216+
unquote(main_body_for(language))
217+
end
218+
219+
defp load_config(config) do
220+
:lists.foreach(fn {app, kw} ->
221+
:lists.foreach(fn {k, v} ->
224222
:application.set_env(app, k, v, persistent: true)
225-
end
226-
:ok
227-
end
223+
end, kw)
224+
end, config)
225+
:ok
226+
end
228227

229-
defp start_app(nil) do
230-
:ok
231-
end
228+
defp start_app(nil) do
229+
:ok
230+
end
232231

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
232+
defp start_app(app) do
233+
case :application.ensure_all_started(app) do
234+
{:ok, _} -> :ok
235+
{:error, {app, reason}} ->
236+
formatted_error = case :code.ensure_loaded(Application) do
237+
{:module, Application} -> Application.format_error(reason)
238+
{:error, _} -> :io_lib.format('~p', [reason])
239+
end
240+
io_error ["Could not start application ",
241+
:erlang.atom_to_binary(app, :utf8),
242+
": ", formatted_error, ?\n]
243+
:erlang.halt(1)
241244
end
245+
end
242246

243-
defp io_error(message) do
244-
IO.puts :stderr, IO.ANSI.format([:red, :bright, message])
245-
end
247+
defp io_error(message) do
248+
:io.put_chars(:standard_error, message)
246249
end
250+
end
247251

252+
{:module, ^name, binary, _} = Module.create(name, module_body, Macro.Env.location(__ENV__))
248253
[{'#{name}.beam', binary}]
249254
end
255+
256+
defp main_body_for(:elixir) do
257+
quote do
258+
case :application.ensure_all_started(:elixir) do
259+
{:ok, _} ->
260+
load_config(@config)
261+
start_app(@app)
262+
args = Enum.map(args, &List.to_string(&1))
263+
Kernel.CLI.run fn _ -> @module.main(args) end, true
264+
_ ->
265+
:io.put_chars :standard_error, "Elixir is not available, aborting.\n"
266+
:erlang.halt(1)
267+
end
268+
end
269+
end
270+
271+
defp main_body_for(:erlang) do
272+
quote do
273+
load_config(@config)
274+
start_app(@app)
275+
@module.main(args)
276+
end
277+
end
250278
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)