Skip to content

Commit ae19491

Browse files
committed
Merge pull request #2701 from alco/escript-protocol-consolidation
Escript protocol consolidation
2 parents 0b38bc0 + 64f3760 commit ae19491

File tree

6 files changed

+97
-23
lines changed

6 files changed

+97
-23
lines changed

lib/mix/lib/mix/tasks/compile.protocols.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ defmodule Mix.Tasks.Compile.Protocols do
3535
paths = filter_otp(:code.get_path, :code.lib_dir)
3636
paths
3737
|> Protocol.extract_protocols
38-
|> consolidate(paths, opts[:output] || Path.join(Mix.Project.build_path, "consolidated"))
38+
|> consolidate(paths, opts[:output] || default_path())
3939

4040
:ok
4141
end
4242

43+
@doc false
44+
def default_path, do: Path.join(Mix.Project.build_path, "consolidated")
45+
4346
defp filter_otp(paths, otp) do
4447
Enum.filter(paths, &(not :lists.prefix(&1, otp)))
4548
end

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

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ 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+
49+
Defaults to `true` for Elixir projects.
50+
51+
* `:consolidate_protocols` - if `true`, all protocols will be consolidated
52+
before being embedded into the escript.
53+
4854
Defaults to `true` for Elixir projects.
4955
5056
* `:shebang` - shebang interpreter directive used to execute the escript.
@@ -86,18 +92,26 @@ defmodule Mix.Tasks.Escript.Build do
8692
Mix.Task.run :compile, args
8793
end
8894

89-
escriptize(Mix.Project.config, opts[:force])
95+
project = Mix.Project.config
96+
language = Keyword.get(project, :language, :elixir)
97+
should_consolidate =
98+
Keyword.get(project, :consolidate_protocols, language == :elixir)
99+
100+
if should_consolidate do
101+
Mix.Task.run "compile.protocols", []
102+
end
103+
104+
escriptize(project, language, opts[:force], should_consolidate)
90105
end
91106

92-
defp escriptize(project, force) do
107+
defp escriptize(project, language, force, should_consolidate) do
93108
escript_opts = project[:escript] || []
94109

95110
script_name = to_string(escript_opts[:name] || project[:app])
96111
filename = escript_opts[:path] || script_name
97112
main = escript_opts[:main_module]
98113
app = Keyword.get(escript_opts, :app, project[:app])
99114
files = project_files()
100-
language = Keyword.get(project, :language, :elixir)
101115

102116
escript_mod = String.to_atom(Atom.to_string(app) <> "_escript")
103117

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

113127
force || Mix.Utils.stale?(files, [filename]) ->
128+
beam_paths =
129+
[files, deps_files(), core_files(escript_opts, language)]
130+
|> Stream.concat
131+
|> prepare_beam_paths
132+
133+
if should_consolidate do
134+
beam_paths =
135+
Path.wildcard(consolidated_path <> "/*")
136+
|> prepare_beam_paths(beam_paths)
137+
end
138+
114139
tuples = gen_main(escript_mod, main, app, language) ++
115-
to_tuples(files) ++ deps_tuples() ++
116-
embed_tuples(escript_opts, language)
140+
read_beams(beam_paths)
117141

118142
case :zip.create 'mem', tuples, [:memory] do
119143
{:ok, {'mem', zip}} ->
@@ -136,7 +160,7 @@ defmodule Mix.Tasks.Escript.Build do
136160
end
137161
end
138162

139-
defp project_files do
163+
defp project_files() do
140164
get_files(Mix.Project.app_path)
141165
end
142166

@@ -145,29 +169,19 @@ defmodule Mix.Tasks.Escript.Build do
145169
(Path.wildcard("#{app}/priv/**/*") |> Enum.filter(&File.regular?/1))
146170
end
147171

148-
defp get_tuples(app) do
149-
get_files(app) |> to_tuples
150-
end
151-
152-
defp to_tuples(files) do
153-
for f <- files do
154-
{String.to_char_list(Path.basename(f)), File.read!(f)}
155-
end
156-
end
157-
158172
defp set_perms(filename) do
159173
stat = File.stat!(filename)
160174
:ok = File.chmod(filename, stat.mode ||| 0o111)
161175
end
162176

163-
defp deps_tuples do
177+
defp deps_files() do
164178
deps = Mix.Dep.loaded(env: Mix.env) || []
165-
Enum.flat_map(deps, fn dep -> get_tuples(dep.opts[:build]) end)
179+
Enum.flat_map(deps, fn dep -> get_files(dep.opts[:build]) end)
166180
end
167181

168-
defp embed_tuples(escript_opts, language) do
182+
defp core_files(escript_opts, language) do
169183
if Keyword.get(escript_opts, :embed_elixir, language == :elixir) do
170-
Enum.flat_map [:elixir|extra_apps()], &app_tuples(&1)
184+
Enum.flat_map [:elixir|extra_apps()], &app_files/1
171185
else
172186
[]
173187
end
@@ -184,13 +198,28 @@ defmodule Mix.Tasks.Escript.Build do
184198
Enum.filter(extra_apps || [], &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
185199
end
186200

187-
defp app_tuples(app) do
201+
defp app_files(app) do
188202
case :code.where_is_file('#{app}.app') do
189203
:non_existing -> Mix.raise "Could not find application #{app}"
190-
file -> get_tuples(Path.dirname(Path.dirname(file)))
204+
file -> get_files(Path.dirname(Path.dirname(file)))
191205
end
192206
end
193207

208+
defp prepare_beam_paths(paths, dict \\ HashDict.new) do
209+
paths
210+
|> Enum.map(&{Path.basename(&1), &1})
211+
|> Enum.into(dict)
212+
end
213+
214+
defp read_beams(items) do
215+
items
216+
|> Enum.map(fn {basename, beam_path} ->
217+
{String.to_char_list(basename), File.read!(beam_path)}
218+
end)
219+
end
220+
221+
defp consolidated_path, do: Mix.Tasks.Compile.Protocols.default_path
222+
194223
defp build_comment(user_comment) do
195224
"%% #{user_comment}\n"
196225
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
defprotocol DepProto do
2+
def hello(x)
3+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule DepProto.Mixfile do
2+
use Mix.Project
3+
4+
def project do
5+
[ app: :depproto,
6+
version: "0.1.0" ]
7+
end
8+
end
9+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule Escripttest do
2+
def main([protocol]) do
3+
IO.puts Protocol.consolidated?(Module.concat([protocol]))
4+
end
5+
end

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ defmodule Mix.Tasks.EscriptTest do
5555
end
5656
end
5757

58+
defmodule EscriptConsolidated do
59+
def project do
60+
[ app: :escripttestconsolidated,
61+
version: "0.0.1",
62+
escript: [
63+
main_module: Escripttest,
64+
path: Path.join("ebin", "escripttestconsolidated"),
65+
],
66+
deps: [{:depproto, path: fixture_path("dep_with_protocol")}] ]
67+
end
68+
end
69+
5870
test "generate escript" do
5971
Mix.Project.push Escript
6072

@@ -115,4 +127,17 @@ defmodule Mix.Tasks.EscriptTest do
115127
after
116128
purge [Ok.Mixfile]
117129
end
130+
131+
test "generate escript with consolidated protocols" do
132+
Mix.Project.push EscriptConsolidated
133+
134+
in_fixture "escripttest_protocols", fn ->
135+
Mix.Tasks.Escript.Build.run []
136+
assert_received {:mix_shell, :info, ["Generated escript ebin/escripttestconsolidated with MIX_ENV=dev"]}
137+
assert System.cmd("escript", ["ebin/escripttestconsolidated", "Enumerable"]) == {"true\n", 0}
138+
assert System.cmd("escript", ["ebin/escripttestconsolidated", "DepProto"]) == {"true\n", 0}
139+
end
140+
after
141+
purge [DepProto.Mixfile]
142+
end
118143
end

0 commit comments

Comments
 (0)