Skip to content

Commit 737162c

Browse files
committed
Merge protocol consolidation into compile.elixir
1 parent 4285cea commit 737162c

File tree

9 files changed

+328
-340
lines changed

9 files changed

+328
-340
lines changed

lib/mix/lib/mix.ex

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,7 @@ defmodule Mix do
704704
* `:verbose` - if `true`, prints additional debugging information
705705
(Default: `false`)
706706
707-
* `:consolidate_protocols` - if `true`, runs protocol
708-
consolidation via the `mix compile.protocols` task (Default: `true`)
707+
* `:consolidate_protocols` - if `true`, runs protocol consolidation (Default: `true`)
709708
710709
* `:elixir` - if set, ensures the current Elixir version matches the given
711710
version requirement (Default: `nil`)

lib/mix/lib/mix/compilers/elixir.ex

Lines changed: 93 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Mix.Compilers.Elixir do
22
@moduledoc false
33

4-
@manifest_vsn 26
4+
@manifest_vsn 27
55
@checkpoint_vsn 2
66

77
import Record
@@ -46,9 +46,8 @@ defmodule Mix.Compilers.Elixir do
4646
all_paths = Mix.Utils.extract_files(srcs, [:ex])
4747

4848
{all_modules, all_sources, all_local_exports, old_parents, old_cache_key, old_deps_config,
49-
old_project_mtime,
50-
old_config_mtime} =
51-
parse_manifest(manifest, dest)
49+
old_project_mtime, old_config_mtime,
50+
old_protocols_and_impls} = parse_manifest(manifest, dest)
5251

5352
# Prepend ourselves early because of __mix_recompile__? checks
5453
# and also that, in case of nothing compiled, we already need
@@ -129,7 +128,7 @@ defmodule Mix.Compilers.Elixir do
129128
{false, stale, old_deps_config}
130129
end
131130

132-
{stale_modules, stale_exports, all_local_exports} =
131+
{stale_modules, stale_exports, all_local_exports, protocols_and_impls} =
133132
stale_local_deps(local_deps, manifest, stale, modified, all_local_exports)
134133

135134
prev_paths = Map.keys(all_sources)
@@ -157,26 +156,41 @@ defmodule Mix.Compilers.Elixir do
157156
{sources, removed_modules} =
158157
update_stale_sources(sources, stale, removed_modules, sources_stats)
159158

160-
if stale != [] or stale_modules != %{} do
159+
consolidation_status =
160+
if Mix.Project.umbrella?() do
161+
:off
162+
else
163+
Mix.Compilers.Protocol.status(config_mtime > old_config_mtime, opts)
164+
end
165+
166+
if stale != [] or stale_modules != %{} or removed != [] or deps_changed? or
167+
consolidation_status == :force do
161168
path = opts[:purge_consolidation_path_if_stale]
162169

163170
if is_binary(path) and Code.delete_path(path) do
164171
purge_modules_in_path(path)
165172
end
166173

167-
Mix.Utils.compiling_n(length(stale), :ex)
174+
if stale != [] do
175+
Mix.Utils.compiling_n(length(stale), :ex)
176+
end
177+
168178
Mix.Project.ensure_structure()
169179

170180
# We don't want to cache this path as we will write to it
171181
true = Code.prepend_path(dest)
172182
previous_opts = set_compiler_opts(opts)
173183

184+
# Group consolidation information to pass to compiler
185+
174186
try do
175-
state = {%{}, exports, sources, [], modules, removed_modules}
187+
consolidation = {consolidation_status, old_protocols_and_impls, protocols_and_impls}
188+
state = {%{}, exports, sources, [], modules, removed_modules, consolidation}
176189
compiler_loop(stale, stale_modules, dest, timestamp, opts, state)
177190
else
178191
{:ok, %{runtime_warnings: runtime_warnings, compile_warnings: compile_warnings}, state} ->
179-
{modules, _exports, sources, _changed, pending_modules, _stale_exports} = state
192+
{modules, _exports, sources, _changed, pending_modules, _stale_exports,
193+
protocols_and_impls} = state
180194

181195
previous_warnings =
182196
if Keyword.get(opts, :all_warnings, true),
@@ -197,6 +211,7 @@ defmodule Mix.Compilers.Elixir do
197211
new_deps_config,
198212
project_mtime,
199213
config_mtime,
214+
protocols_and_impls,
200215
timestamp
201216
)
202217

@@ -208,12 +223,11 @@ defmodule Mix.Compilers.Elixir do
208223
end
209224

210225
Mix.Task.Compiler.notify_modules_compiled(lazy_modules_diff)
211-
212226
unless_previous_warnings_as_errors(previous_warnings, opts, {:ok, all_warnings})
213227

214228
{:error, errors, %{runtime_warnings: r_warnings, compile_warnings: c_warnings}, state} ->
215229
# In case of errors, we show all previous warnings and all new ones.
216-
{_, _, sources, _, _, _} = state
230+
{_, _, sources, _, _, _, _} = state
217231
errors = Enum.map(errors, &diagnostic/1)
218232
warnings = Enum.map(r_warnings ++ c_warnings, &diagnostic/1)
219233
all_warnings = Keyword.get(opts, :all_warnings, errors == [])
@@ -222,40 +236,9 @@ defmodule Mix.Compilers.Elixir do
222236
Code.compiler_options(previous_opts)
223237
end
224238
else
225-
# We need to return ok if deps_changed? or stale_modules changed,
226-
# even if no code was compiled, because we need to propagate the changed
227-
# status to compile.protocols. This will be the case whenever:
228-
#
229-
# * the lock file or a config changes
230-
# * any module in a path dependency changes
231-
# * the mix.exs changes
232-
# * the Erlang manifest updates (Erlang files are compiled)
233-
#
234-
# In the first case, we will consolidate from scratch. In the remaining, we
235-
# will only compute the diff with current protocols. In fact, there is no
236-
# need to reconsolidate if an Erlang file changes and it doesn't trigger
237-
# any other change, but the diff check should be reasonably fast anyway.
238-
status = if removed != [] or deps_changed? or stale_modules != %{}, do: :ok, else: :noop
239-
240-
if status != :noop do
241-
write_manifest(
242-
manifest,
243-
modules,
244-
sources,
245-
all_local_exports,
246-
new_parents,
247-
new_cache_key,
248-
new_deps_config,
249-
project_mtime,
250-
config_mtime,
251-
timestamp
252-
)
253-
end
254-
255239
all_warnings = Keyword.get(opts, :all_warnings, true)
256240
previous_warnings = previous_warnings(sources, all_warnings)
257-
258-
unless_previous_warnings_as_errors(previous_warnings, opts, {status, previous_warnings})
241+
unless_previous_warnings_as_errors(previous_warnings, opts, {:noop, previous_warnings})
259242
end
260243
end
261244

@@ -292,24 +275,6 @@ defmodule Mix.Compilers.Elixir do
292275
end)
293276
end
294277

295-
@doc """
296-
Returns protocols and implementations for the given `manifest`.
297-
"""
298-
def protocols_and_impls(paths) do
299-
Enum.reduce(paths, {%{}, %{}}, fn path, acc ->
300-
{modules, _} = read_manifest(Path.join(path, ".mix/compile.elixir"))
301-
302-
Enum.reduce(modules, acc, fn
303-
{module, module(kind: kind, timestamp: timestamp)}, {protocols, impls} ->
304-
case kind do
305-
:protocol -> {Map.put(protocols, module, timestamp), impls}
306-
{:impl, protocol} -> {protocols, Map.put(impls, module, protocol)}
307-
_ -> {protocols, impls}
308-
end
309-
end)
310-
end)
311-
end
312-
313278
@doc """
314279
Reads the manifest for external consumption.
315280
"""
@@ -328,7 +293,7 @@ defmodule Mix.Compilers.Elixir do
328293
Retrieves all diagnostics from the given manifest.
329294
"""
330295
def diagnostics(manifest, dest) do
331-
{_, all_sources, _, _, _, _, _, _} = parse_manifest(manifest, dest)
296+
{_, all_sources, _, _, _, _, _, _, _} = parse_manifest(manifest, dest)
332297
previous_warnings(all_sources, false)
333298
end
334299

@@ -619,8 +584,8 @@ defmodule Mix.Compilers.Elixir do
619584
for %{app: app, opts: opts} <- local_deps,
620585
manifest = Path.join([opts[:build], ".mix", base]),
621586
Mix.Utils.last_modified(manifest) > modified,
622-
reduce: {stale_modules, stale_modules, deps_exports} do
623-
{modules, exports, deps_exports} ->
587+
reduce: {stale_modules, stale_modules, deps_exports, protocols_and_impls()} do
588+
{modules, exports, deps_exports, protocols_and_impls} ->
624589
{manifest_modules, manifest_sources} = read_manifest(manifest)
625590

626591
dep_modules =
@@ -673,7 +638,8 @@ defmodule Mix.Compilers.Elixir do
673638
modules = modules |> Map.merge(dep_modules) |> Map.merge(removed)
674639
exports = Map.merge(exports, removed)
675640
deps_exports = Map.put(deps_exports, app, new_exports)
676-
{modules, exports, deps_exports}
641+
protocols_and_impls = protocols_and_impls_from_modules(modules, protocols_and_impls)
642+
{modules, exports, deps_exports, protocols_and_impls}
677643
end
678644
end
679645

@@ -879,7 +845,7 @@ defmodule Mix.Compilers.Elixir do
879845

880846
## Manifest handling
881847

882-
@default_manifest {%{}, %{}, %{}, [], nil, nil, 0, 0}
848+
@default_manifest {%{}, %{}, %{}, [], nil, nil, 0, 0, {%{}, %{}}}
883849

884850
# Similar to read_manifest, but for internal consumption and with data migration support.
885851
defp parse_manifest(manifest, compile_path) do
@@ -890,9 +856,9 @@ defmodule Mix.Compilers.Elixir do
890856
@default_manifest
891857
else
892858
{@manifest_vsn, modules, sources, local_exports, parent, cache_key, deps_config,
893-
project_mtime, config_mtime} ->
859+
project_mtime, config_mtime, protocols_and_impls} ->
894860
{modules, sources, local_exports, parent, cache_key, deps_config, project_mtime,
895-
config_mtime}
861+
config_mtime, protocols_and_impls}
896862

897863
# {vsn, %{module => record}, sources, ...} v22-?
898864
# {vsn, [module_record], sources, ...} v5-v21
@@ -946,6 +912,7 @@ defmodule Mix.Compilers.Elixir do
946912
deps_config,
947913
project_mtime,
948914
config_mtime,
915+
protocols_and_impls,
949916
timestamp
950917
) do
951918
if modules == %{} and sources == %{} do
@@ -955,7 +922,7 @@ defmodule Mix.Compilers.Elixir do
955922

956923
term =
957924
{@manifest_vsn, modules, sources, exports, parents, cache_key, deps_config, project_mtime,
958-
config_mtime}
925+
config_mtime, protocols_and_impls}
959926

960927
manifest_data = :erlang.term_to_binary(term, [:compressed])
961928
File.write!(manifest, manifest_data)
@@ -1055,7 +1022,7 @@ defmodule Mix.Compilers.Elixir do
10551022
spawn_link(fn ->
10561023
compile_opts = [
10571024
each_cycle: fn ->
1058-
compiler_call(parent, ref, {:each_cycle, stale_modules, dest, timestamp})
1025+
compiler_call(parent, ref, {:each_cycle, stale_modules, dest, timestamp, opts})
10591026
end,
10601027
each_file: fn file, lexical ->
10611028
compiler_call(parent, ref, {:each_file, file, lexical, verbose})
@@ -1092,8 +1059,8 @@ defmodule Mix.Compilers.Elixir do
10921059

10931060
defp compiler_loop(ref, pid, state, cwd) do
10941061
receive do
1095-
{^ref, {:each_cycle, stale_modules, dest, timestamp}} ->
1096-
{response, state} = each_cycle(stale_modules, dest, timestamp, state)
1062+
{^ref, {:each_cycle, stale_modules, dest, timestamp, opts}} ->
1063+
{response, state} = each_cycle(stale_modules, dest, timestamp, state, opts)
10971064
send(pid, {ref, response})
10981065
compiler_loop(ref, pid, state, cwd)
10991066

@@ -1121,8 +1088,8 @@ defmodule Mix.Compilers.Elixir do
11211088
end
11221089
end
11231090

1124-
defp each_cycle(stale_modules, compile_path, timestamp, state) do
1125-
{modules, _exports, sources, changed, pending_modules, stale_exports} = state
1091+
defp each_cycle(stale_modules, compile_path, timestamp, state, opts) do
1092+
{modules, _exports, sources, changed, pending_modules, stale_exports, consolidation} = state
11261093

11271094
{pending_modules, exports, changed} =
11281095
update_stale_entries(pending_modules, sources, changed, %{}, stale_exports, compile_path)
@@ -1165,7 +1132,8 @@ defmodule Mix.Compilers.Elixir do
11651132
runtime_paths =
11661133
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
11671134

1168-
state = {modules, exports, sources, [], pending_modules, stale_exports}
1135+
protocols_and_impls = maybe_consolidate(consolidation, modules, opts)
1136+
state = {modules, exports, sources, [], pending_modules, stale_exports, protocols_and_impls}
11691137
{{:runtime, runtime_paths, []}, state}
11701138
else
11711139
Mix.Utils.compiling_n(length(changed), :ex)
@@ -1192,7 +1160,7 @@ defmodule Mix.Compilers.Elixir do
11921160

11931161
defp each_file(file, references, verbose, state, cwd) do
11941162
{compile_references, export_references, runtime_references, compile_env} = references
1195-
{modules, exports, sources, changed, pending_modules, stale_exports} = state
1163+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate} = state
11961164

11971165
file = Path.relative_to(file, cwd)
11981166

@@ -1220,11 +1188,11 @@ defmodule Mix.Compilers.Elixir do
12201188
)
12211189

12221190
sources = Map.replace!(sources, file, source)
1223-
{modules, exports, sources, changed, pending_modules, stale_exports}
1191+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate}
12241192
end
12251193

12261194
defp each_module(file, module, kind, external, new_export, state, timestamp, cwd) do
1227-
{modules, exports, sources, changed, pending_modules, stale_exports} = state
1195+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate} = state
12281196

12291197
file = Path.relative_to(file, cwd)
12301198
external = process_external_resources(external, cwd)
@@ -1277,7 +1245,7 @@ defmodule Mix.Compilers.Elixir do
12771245
%{} -> changed
12781246
end
12791247

1280-
{modules, exports, sources, changed, pending_modules, stale_exports}
1248+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate}
12811249
end
12821250

12831251
defp detect_kind(module) do
@@ -1306,4 +1274,50 @@ defmodule Mix.Compilers.Elixir do
13061274
{Path.relative_to(file, cwd), Mix.Utils.last_modified_and_size(file), digest}
13071275
end
13081276
end
1277+
1278+
## Consolidation
1279+
1280+
@doc """
1281+
Returns protocols and implementations for the given `manifest`.
1282+
"""
1283+
def protocols_and_impls_from_paths(paths) do
1284+
Enum.reduce(paths, protocols_and_impls(), fn path, acc ->
1285+
{modules, _} = read_manifest(Path.join(path, ".mix/compile.elixir"))
1286+
protocols_and_impls_from_modules(modules, acc)
1287+
end)
1288+
end
1289+
1290+
defp protocols_and_impls_from_modules(modules, protocols_and_impls) do
1291+
Enum.reduce(modules, protocols_and_impls, fn
1292+
{module, module(kind: kind, timestamp: timestamp)}, {protocols, impls} ->
1293+
case kind do
1294+
:protocol -> {Map.put(protocols, module, timestamp), impls}
1295+
{:impl, protocol} -> {protocols, Map.put(impls, module, protocol)}
1296+
_ -> {protocols, impls}
1297+
end
1298+
end)
1299+
end
1300+
1301+
defp protocols_and_impls(), do: {%{}, %{}}
1302+
1303+
defp maybe_consolidate({:off, _, _}, _, _) do
1304+
protocols_and_impls()
1305+
end
1306+
1307+
defp maybe_consolidate(
1308+
{on_or_force, old_protocols_and_impls, protocols_and_impls},
1309+
modules,
1310+
opts
1311+
) do
1312+
protocols_and_impls = protocols_and_impls_from_modules(modules, protocols_and_impls)
1313+
1314+
Mix.Compilers.Protocol.compile(
1315+
on_or_force == :force,
1316+
old_protocols_and_impls,
1317+
protocols_and_impls,
1318+
opts
1319+
)
1320+
1321+
protocols_and_impls
1322+
end
13091323
end

0 commit comments

Comments
 (0)