Skip to content

Commit e2dc9ae

Browse files
committed
Do not write files to disk if warnings as errors was triggered, closes #9808
1 parent 51c95b6 commit e2dc9ae

File tree

2 files changed

+85
-73
lines changed

2 files changed

+85
-73
lines changed

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,15 @@ defmodule Kernel.ParallelCompiler do
139139
compiler_pid = self()
140140
:elixir_code_server.cast({:reset_warnings, compiler_pid})
141141
schedulers = max(:erlang.system_info(:schedulers_online), 2)
142+
beam_timestamp = Keyword.get(options, :beam_timestamp)
142143

143-
result =
144+
outcome =
144145
spawn_workers(files, 0, [], [], %{}, [], %{
145146
dest: Keyword.get(options, :dest),
146147
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, []} end),
147148
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
148149
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
149150
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
150-
beam_timestamp: Keyword.get(options, :beam_timestamp),
151151
long_compilation_threshold: Keyword.get(options, :long_compilation_threshold, 15),
152152
profile: Keyword.get(options, :profile),
153153
cycle_start: System.monotonic_time(),
@@ -160,7 +160,7 @@ defmodule Kernel.ParallelCompiler do
160160
# compilation status will be set to error.
161161
compilation_status = :elixir_code_server.call({:compilation_status, compiler_pid})
162162

163-
case {result, compilation_status} do
163+
case {outcome, compilation_status} do
164164
{{:ok, _, warnings}, :error} ->
165165
message = "Compilation failed due to warnings while using the --warnings-as-errors option"
166166
IO.puts(:stderr, message)
@@ -169,8 +169,11 @@ defmodule Kernel.ParallelCompiler do
169169
{{:error, errors, warnings}, :error} ->
170170
{:error, errors ++ warnings, []}
171171

172-
_ ->
173-
result
172+
{{:ok, outcome, warnings}, _} ->
173+
{:ok, write_module_binaries(outcome, output, beam_timestamp), warnings}
174+
175+
{{:error, errors, warnings}, _} ->
176+
{:error, errors, warnings}
174177
end
175178
end
176179

@@ -187,6 +190,74 @@ defmodule Kernel.ParallelCompiler do
187190
end
188191
end
189192

193+
defp write_module_binaries(result, {:compile, path}, timestamp) do
194+
Enum.flat_map(result, fn
195+
{{:module, module}, {binary, _map}} ->
196+
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
197+
File.write!(full_path, binary)
198+
if timestamp, do: File.touch!(full_path, timestamp)
199+
[module]
200+
201+
_ ->
202+
[]
203+
end)
204+
end
205+
206+
defp write_module_binaries(result, _output, _timestamp) do
207+
for {{:module, module}, _} <- result, do: module
208+
end
209+
210+
## Verification
211+
212+
defp verify_modules(result, warnings, dependent_modules, state) do
213+
checker_warnings = maybe_check_modules(result, dependent_modules, state)
214+
warnings = Enum.reverse(warnings, checker_warnings)
215+
{:ok, result, warnings}
216+
end
217+
218+
defp maybe_check_modules(result, runtime_modules, state) do
219+
%{schedulers: schedulers, profile: profile} = state
220+
221+
if :elixir_config.get(:bootstrap) do
222+
[]
223+
else
224+
compiled_modules = checker_compiled_modules(result)
225+
runtime_modules = checker_runtime_modules(runtime_modules)
226+
227+
profile_checker(profile, compiled_modules, runtime_modules, fn ->
228+
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
229+
end)
230+
end
231+
end
232+
233+
defp checker_compiled_modules(result) do
234+
for {{:module, _module}, {binary, module_map}} <- result do
235+
{module_map, binary}
236+
end
237+
end
238+
239+
defp checker_runtime_modules(modules) do
240+
for module <- modules,
241+
path = :code.which(module),
242+
is_list(path) do
243+
{module, File.read!(path)}
244+
end
245+
end
246+
247+
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
248+
{time, result} = :timer.tc(fun)
249+
time = div(time, 1000)
250+
num_modules = length(compiled_modules) + length(runtime_modules)
251+
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
252+
result
253+
end
254+
255+
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
256+
fun.()
257+
end
258+
259+
## Compiler worker spawning
260+
190261
# We already have n=schedulers currently running, don't spawn new ones
191262
defp spawn_workers(
192263
queue,
@@ -253,10 +324,10 @@ defmodule Kernel.ParallelCompiler do
253324

254325
case each_cycle_return(state.each_cycle.()) do
255326
{:runtime, dependent_modules} ->
256-
write_and_verify_modules(result, warnings, dependent_modules, state)
327+
verify_modules(result, warnings, dependent_modules, state)
257328

258329
{:compile, []} ->
259-
write_and_verify_modules(result, warnings, [], state)
330+
verify_modules(result, warnings, [], state)
260331

261332
{:compile, more} ->
262333
spawn_workers(more, 0, [], [], result, warnings, state)
@@ -354,71 +425,6 @@ defmodule Kernel.ParallelCompiler do
354425
defp each_cycle_return(modules) when is_list(modules), do: {:compile, modules}
355426
defp each_cycle_return(other), do: other
356427

357-
defp write_and_verify_modules(result, warnings, dependent_modules, state) do
358-
modules = write_module_binaries(result, state)
359-
checker_warnings = maybe_check_modules(result, dependent_modules, state)
360-
warnings = Enum.reverse(warnings, checker_warnings)
361-
{:ok, modules, warnings}
362-
end
363-
364-
defp write_module_binaries(result, %{output: {:compile, path}, beam_timestamp: timestamp}) do
365-
Enum.flat_map(result, fn
366-
{{:module, module}, {binary, _map}} ->
367-
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
368-
File.write!(full_path, binary)
369-
if timestamp, do: File.touch!(full_path, timestamp)
370-
[module]
371-
372-
_ ->
373-
[]
374-
end)
375-
end
376-
377-
defp write_module_binaries(result, _state) do
378-
for {{:module, module}, _} <- result, do: module
379-
end
380-
381-
defp maybe_check_modules(result, runtime_modules, state) do
382-
%{schedulers: schedulers, profile: profile} = state
383-
384-
if :elixir_config.get(:bootstrap) do
385-
[]
386-
else
387-
compiled_modules = checker_compiled_modules(result)
388-
runtime_modules = checker_runtime_modules(runtime_modules)
389-
390-
profile_checker(profile, compiled_modules, runtime_modules, fn ->
391-
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
392-
end)
393-
end
394-
end
395-
396-
defp checker_compiled_modules(result) do
397-
for {{:module, _module}, {binary, module_map}} <- result do
398-
{module_map, binary}
399-
end
400-
end
401-
402-
defp checker_runtime_modules(modules) do
403-
for module <- modules,
404-
path = :code.which(module),
405-
is_list(path) do
406-
{module, File.read!(path)}
407-
end
408-
end
409-
410-
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
411-
{time, result} = :timer.tc(fun)
412-
time = div(time, 1000)
413-
num_modules = length(compiled_modules) + length(runtime_modules)
414-
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
415-
result
416-
end
417-
418-
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
419-
fun.()
420-
end
421-
422428
# The goal of this function is to find leaves in the dependency graph,
423429
# i.e. to find code that depends on code that we know is not being defined.
424430
defp without_definition(waiting, files) do

lib/elixir/test/elixir/kernel/parallel_compiler_test.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,16 @@ defmodule Kernel.ParallelCompilerTest do
278278
"""
279279
)
280280

281+
output = tmp_path("not_to_be_used")
282+
281283
try do
282284
Code.compiler_options(warnings_as_errors: true)
283285

284286
msg =
285287
capture_io(:stderr, fn ->
286-
assert {:error, [error], []} = Kernel.ParallelCompiler.compile([fixture])
288+
assert {:error, [error], []} =
289+
Kernel.ParallelCompiler.compile_to_path([fixture], output)
290+
287291
msg = "this clause cannot match because a previous clause at line 2 always matches"
288292
assert error == {fixture, 3, msg}
289293
end)
@@ -294,6 +298,8 @@ defmodule Kernel.ParallelCompilerTest do
294298
Code.compiler_options(warnings_as_errors: warnings_as_errors)
295299
purge([WarningsSample])
296300
end
301+
302+
refute File.exists?(output)
297303
end
298304

299305
test "does not use incorrect line number when error originates in another file" do

0 commit comments

Comments
 (0)