Skip to content

Commit 64dd1d9

Browse files
committed
Merge protocol consolidation into compile.elixir
1 parent f9f61bb commit 64dd1d9

File tree

9 files changed

+331
-337
lines changed

9 files changed

+331
-337
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: 96 additions & 76 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

@@ -217,7 +232,7 @@ defmodule Mix.Compilers.Elixir do
217232
else: {errors, r_warnings ++ c_warnings}
218233

219234
# In case of errors, we show all previous warnings and all new ones.
220-
{_, _, sources, _, _, _} = state
235+
{_, _, sources, _, _, _, _} = state
221236
errors = Enum.map(errors, &diagnostic/1)
222237
warnings = Enum.map(warnings, &diagnostic/1)
223238
all_warnings = Keyword.get(opts, :all_warnings, errors == [])
@@ -226,39 +241,13 @@ defmodule Mix.Compilers.Elixir do
226241
Code.compiler_options(previous_opts)
227242
end
228243
else
229-
# We need to return ok if deps_changed? or stale_modules changed,
230-
# even if no code was compiled, because we need to propagate the changed
231-
# status to compile.protocols. This will be the case whenever:
232-
#
233-
# * the lock file or a config changes
234-
# * any module in a path dependency changes
235-
# * the mix.exs changes
236-
# * the Erlang manifest updates (Erlang files are compiled)
237-
#
238-
# In the first case, we will consolidate from scratch. In the remaining, we
239-
# will only compute the diff with current protocols. In fact, there is no
240-
# need to reconsolidate if an Erlang file changes and it doesn't trigger
241-
# any other change, but the diff check should be reasonably fast anyway.
242-
status = if removed != [] or deps_changed? or stale_modules != %{}, do: :ok, else: :noop
243-
244-
if status != :noop do
245-
write_manifest(
246-
manifest,
247-
modules,
248-
sources,
249-
all_local_exports,
250-
new_parents,
251-
new_cache_key,
252-
new_deps_config,
253-
project_mtime,
254-
config_mtime,
255-
timestamp
256-
)
257-
end
258-
259244
all_warnings = Keyword.get(opts, :all_warnings, true)
260245
previous_warnings = previous_warnings(sources, all_warnings)
246+
<<<<<<< HEAD
261247
unless_warnings_as_errors(opts, {status, previous_warnings})
248+
=======
249+
unless_previous_warnings_as_errors(previous_warnings, opts, {:noop, previous_warnings})
250+
>>>>>>> 737162c52 (Merge protocol consolidation into compile.elixir)
262251
end
263252
end
264253

@@ -295,24 +284,6 @@ defmodule Mix.Compilers.Elixir do
295284
end)
296285
end
297286

298-
@doc """
299-
Returns protocols and implementations for the given `manifest`.
300-
"""
301-
def protocols_and_impls(paths) do
302-
Enum.reduce(paths, {%{}, %{}}, fn path, acc ->
303-
{modules, _} = read_manifest(Path.join(path, ".mix/compile.elixir"))
304-
305-
Enum.reduce(modules, acc, fn
306-
{module, module(kind: kind, timestamp: timestamp)}, {protocols, impls} ->
307-
case kind do
308-
:protocol -> {Map.put(protocols, module, timestamp), impls}
309-
{:impl, protocol} -> {protocols, Map.put(impls, module, protocol)}
310-
_ -> {protocols, impls}
311-
end
312-
end)
313-
end)
314-
end
315-
316287
@doc """
317288
Reads the manifest for external consumption.
318289
"""
@@ -331,7 +302,7 @@ defmodule Mix.Compilers.Elixir do
331302
Retrieves all diagnostics from the given manifest.
332303
"""
333304
def diagnostics(manifest, dest) do
334-
{_, all_sources, _, _, _, _, _, _} = parse_manifest(manifest, dest)
305+
{_, all_sources, _, _, _, _, _, _, _} = parse_manifest(manifest, dest)
335306
previous_warnings(all_sources, false)
336307
end
337308

@@ -622,8 +593,8 @@ defmodule Mix.Compilers.Elixir do
622593
for %{app: app, opts: opts} <- local_deps,
623594
manifest = Path.join([opts[:build], ".mix", base]),
624595
Mix.Utils.last_modified(manifest) > modified,
625-
reduce: {stale_modules, stale_modules, deps_exports} do
626-
{modules, exports, deps_exports} ->
596+
reduce: {stale_modules, stale_modules, deps_exports, protocols_and_impls()} do
597+
{modules, exports, deps_exports, protocols_and_impls} ->
627598
{manifest_modules, manifest_sources} = read_manifest(manifest)
628599

629600
dep_modules =
@@ -676,7 +647,8 @@ defmodule Mix.Compilers.Elixir do
676647
modules = modules |> Map.merge(dep_modules) |> Map.merge(removed)
677648
exports = Map.merge(exports, removed)
678649
deps_exports = Map.put(deps_exports, app, new_exports)
679-
{modules, exports, deps_exports}
650+
protocols_and_impls = protocols_and_impls_from_modules(modules, protocols_and_impls)
651+
{modules, exports, deps_exports, protocols_and_impls}
680652
end
681653
end
682654

@@ -882,7 +854,7 @@ defmodule Mix.Compilers.Elixir do
882854

883855
## Manifest handling
884856

885-
@default_manifest {%{}, %{}, %{}, [], nil, nil, 0, 0}
857+
@default_manifest {%{}, %{}, %{}, [], nil, nil, 0, 0, {%{}, %{}}}
886858

887859
# Similar to read_manifest, but for internal consumption and with data migration support.
888860
defp parse_manifest(manifest, compile_path) do
@@ -893,9 +865,9 @@ defmodule Mix.Compilers.Elixir do
893865
@default_manifest
894866
else
895867
{@manifest_vsn, modules, sources, local_exports, parent, cache_key, deps_config,
896-
project_mtime, config_mtime} ->
868+
project_mtime, config_mtime, protocols_and_impls} ->
897869
{modules, sources, local_exports, parent, cache_key, deps_config, project_mtime,
898-
config_mtime}
870+
config_mtime, protocols_and_impls}
899871

900872
# {vsn, %{module => record}, sources, ...} v22-?
901873
# {vsn, [module_record], sources, ...} v5-v21
@@ -949,6 +921,7 @@ defmodule Mix.Compilers.Elixir do
949921
deps_config,
950922
project_mtime,
951923
config_mtime,
924+
protocols_and_impls,
952925
timestamp
953926
) do
954927
if modules == %{} and sources == %{} do
@@ -958,7 +931,7 @@ defmodule Mix.Compilers.Elixir do
958931

959932
term =
960933
{@manifest_vsn, modules, sources, exports, parents, cache_key, deps_config, project_mtime,
961-
config_mtime}
934+
config_mtime, protocols_and_impls}
962935

963936
manifest_data = :erlang.term_to_binary(term, [:compressed])
964937
File.write!(manifest, manifest_data)
@@ -1057,7 +1030,7 @@ defmodule Mix.Compilers.Elixir do
10571030
spawn_link(fn ->
10581031
compile_opts = [
10591032
each_cycle: fn ->
1060-
compiler_call(parent, ref, {:each_cycle, stale_modules, dest, timestamp})
1033+
compiler_call(parent, ref, {:each_cycle, stale_modules, dest, timestamp, opts})
10611034
end,
10621035
each_file: fn file, lexical ->
10631036
compiler_call(parent, ref, {:each_file, file, lexical, verbose})
@@ -1093,8 +1066,8 @@ defmodule Mix.Compilers.Elixir do
10931066

10941067
defp compiler_loop(ref, pid, state, cwd) do
10951068
receive do
1096-
{^ref, {:each_cycle, stale_modules, dest, timestamp}} ->
1097-
{response, state} = each_cycle(stale_modules, dest, timestamp, state)
1069+
{^ref, {:each_cycle, stale_modules, dest, timestamp, opts}} ->
1070+
{response, state} = each_cycle(stale_modules, dest, timestamp, state, opts)
10981071
send(pid, {ref, response})
10991072
compiler_loop(ref, pid, state, cwd)
11001073

@@ -1122,8 +1095,8 @@ defmodule Mix.Compilers.Elixir do
11221095
end
11231096
end
11241097

1125-
defp each_cycle(stale_modules, compile_path, timestamp, state) do
1126-
{modules, _exports, sources, changed, pending_modules, stale_exports} = state
1098+
defp each_cycle(stale_modules, compile_path, timestamp, state, opts) do
1099+
{modules, _exports, sources, changed, pending_modules, stale_exports, consolidation} = state
11271100

11281101
{pending_modules, exports, changed} =
11291102
update_stale_entries(pending_modules, sources, changed, %{}, stale_exports, compile_path)
@@ -1166,7 +1139,8 @@ defmodule Mix.Compilers.Elixir do
11661139
runtime_paths =
11671140
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
11681141

1169-
state = {modules, exports, sources, [], pending_modules, stale_exports}
1142+
protocols_and_impls = maybe_consolidate(consolidation, modules, opts)
1143+
state = {modules, exports, sources, [], pending_modules, stale_exports, protocols_and_impls}
11701144
{{:runtime, runtime_paths, []}, state}
11711145
else
11721146
Mix.Utils.compiling_n(length(changed), :ex)
@@ -1193,7 +1167,7 @@ defmodule Mix.Compilers.Elixir do
11931167

11941168
defp each_file(file, references, verbose, state, cwd) do
11951169
{compile_references, export_references, runtime_references, compile_env} = references
1196-
{modules, exports, sources, changed, pending_modules, stale_exports} = state
1170+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate} = state
11971171

11981172
file = Path.relative_to(file, cwd)
11991173

@@ -1221,11 +1195,11 @@ defmodule Mix.Compilers.Elixir do
12211195
)
12221196

12231197
sources = Map.replace!(sources, file, source)
1224-
{modules, exports, sources, changed, pending_modules, stale_exports}
1198+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate}
12251199
end
12261200

12271201
defp each_module(file, module, kind, external, new_export, state, timestamp, cwd) do
1228-
{modules, exports, sources, changed, pending_modules, stale_exports} = state
1202+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate} = state
12291203

12301204
file = Path.relative_to(file, cwd)
12311205
external = process_external_resources(external, cwd)
@@ -1278,7 +1252,7 @@ defmodule Mix.Compilers.Elixir do
12781252
%{} -> changed
12791253
end
12801254

1281-
{modules, exports, sources, changed, pending_modules, stale_exports}
1255+
{modules, exports, sources, changed, pending_modules, stale_exports, consolidate}
12821256
end
12831257

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

0 commit comments

Comments
 (0)