Skip to content

Commit 90296aa

Browse files
JakeBeckerfishcakez
authored andcommitted
Add --all-warnings option to Elixir compiler (#6224)
* Track Elixir build warnings in the manifest * Add --all-warnings option to Elixir compiler * Document "each_warning" option in ParallelCompiler * Handle old manifest version :v6 when parsing manifest * Track warnings during compile using a map in the Agent's state
1 parent bbd42b6 commit 90296aa

File tree

5 files changed

+81
-10
lines changed

5 files changed

+81
-10
lines changed

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ defmodule Kernel.ParallelCompiler do
3030
* `:each_module` - for each module compiled, invokes the callback passing
3131
the file, module and the module bytecode
3232
33+
* `:each_warning` - for each warning, invokes the callback passing
34+
the file, line number, and warning message
35+
3336
* `:dest` - the destination directory for the BEAM files. When using `files/2`,
3437
this information is only used to properly annotate the BEAM files before
3538
they are loaded into memory. If you want a file to actually be written to
@@ -230,6 +233,12 @@ defmodule Kernel.ParallelCompiler do
230233
end
231234
spawn_compilers(state)
232235

236+
{:warning, file, line, message} ->
237+
if callback = Keyword.get(options, :each_warning) do
238+
callback.(file, line, message)
239+
end
240+
wait_for_messages(state)
241+
233242
{:file_compiled, child_pid, file, :ok} ->
234243
discard_down(child_pid)
235244

lib/elixir/src/elixir_errors.erl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
warn(none, File, Warning) ->
1313
warn(0, File, Warning);
1414
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
15+
CompilerPid = get(elixir_compiler_pid),
16+
if
17+
CompilerPid =/= undefined ->
18+
CompilerPid ! {warning, File, Line, Warning};
19+
true -> ok
20+
end,
1521
warn([Warning, "\n ", file_format(Line, File), $\n]).
1622

1723
-spec warn(unicode:chardata()) -> ok.

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

Lines changed: 32 additions & 9 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 :v6
4+
@manifest_vsn :v7
55

66
import Record
77

@@ -13,7 +13,8 @@ defmodule Mix.Compilers.Elixir do
1313
runtime_references: [],
1414
compile_dispatches: [],
1515
runtime_dispatches: [],
16-
external: []
16+
external: [],
17+
warnings: []
1718
]
1819

1920
@doc """
@@ -78,6 +79,8 @@ defmodule Mix.Compilers.Elixir do
7879
stale = changed -- removed
7980
sources = update_stale_sources(all_sources, removed, changed)
8081

82+
if opts[:all_warnings], do: show_warnings(sources)
83+
8184
cond do
8285
stale != [] ->
8386
compile_manifest(manifest, exts, modules, sources, stale, dest, timestamp, opts)
@@ -149,18 +152,19 @@ defmodule Mix.Compilers.Elixir do
149152

150153
# Starts a server responsible for keeping track which files
151154
# were compiled and the dependencies between them.
152-
{:ok, pid} = Agent.start_link(fn -> {modules, sources} end)
155+
{:ok, pid} = Agent.start_link(fn -> {modules, sources, %{}} end)
153156
long_compilation_threshold = opts[:long_compilation_threshold] || 10
154157

155158
try do
156159
_ = Kernel.ParallelCompiler.files stale,
157160
[each_module: &each_module(pid, cwd, &1, &2, &3),
158161
each_long_compilation: &each_long_compilation(&1, long_compilation_threshold),
162+
each_warning: &each_warning(pid, cwd, &1, &2, &3),
159163
long_compilation_threshold: long_compilation_threshold,
160164
dest: dest] ++ extra
161-
Agent.cast pid, fn {modules, sources} ->
165+
Agent.cast pid, fn {modules, sources, warnings} ->
162166
write_manifest(manifest, modules, sources, dest, timestamp)
163-
{modules, sources}
167+
{modules, sources, warnings}
164168
end
165169
after
166170
Agent.stop(pid, :normal, :infinity)
@@ -201,7 +205,7 @@ defmodule Mix.Compilers.Elixir do
201205
source = Path.relative_to(source, cwd)
202206
external = get_external_resources(module, cwd)
203207

204-
Agent.cast pid, fn {modules, sources} ->
208+
Agent.cast pid, fn {modules, sources, warnings} ->
205209
source_external = case List.keyfind(sources, source, source(:source)) do
206210
source(external: old_external) -> external ++ old_external
207211
nil -> external
@@ -227,12 +231,13 @@ defmodule Mix.Compilers.Elixir do
227231
runtime_references: runtime_references,
228232
compile_dispatches: compile_dispatches,
229233
runtime_dispatches: runtime_dispatches,
230-
external: source_external
234+
external: source_external,
235+
warnings: Map.get(warnings, source, [])
231236
)
232237

233238
modules = List.keystore(modules, module, module(:module), new_module)
234239
sources = List.keystore(sources, source, source(:source), new_source)
235-
{modules, sources}
240+
{modules, sources, warnings}
236241
end
237242
end
238243

@@ -262,6 +267,15 @@ defmodule Mix.Compilers.Elixir do
262267
Mix.shell.info "Compiling #{source} (it's taking more than #{threshold}s)"
263268
end
264269

270+
defp each_warning(pid, cwd, file, line, message) do
271+
Agent.cast pid, fn {modules, sources, warnings} ->
272+
source_path = Path.relative_to(file, cwd)
273+
warning = {line, message}
274+
warnings = Map.update(warnings, source_path, [warning], &([warning | &1]))
275+
{modules, sources, warnings}
276+
end
277+
end
278+
265279
## Resolution
266280

267281
defp update_stale_sources(sources, removed, changed) do
@@ -350,6 +364,15 @@ defmodule Mix.Compilers.Elixir do
350364
_ = :code.delete(module)
351365
end
352366

367+
defp show_warnings(sources) do
368+
for source(source: source, warnings: warnings) <- sources do
369+
file = Path.join(File.cwd!, source)
370+
for {line, message} <- warnings do
371+
:elixir_errors.warn(line, file, message)
372+
end
373+
end
374+
end
375+
353376
## Manifest handling
354377

355378
# Similar to read_manifest, but supports data migration.
@@ -361,7 +384,7 @@ defmodule Mix.Compilers.Elixir do
361384
else
362385
[@manifest_vsn | data] ->
363386
split_manifest(data, compile_path)
364-
[v | data] when v in [:v4, :v5] ->
387+
[v | data] when v in [:v4, :v5, :v6] ->
365388
for module(beam: beam) <- data, do: File.rm(Path.join(compile_path, beam))
366389
{[], []}
367390
_ ->

lib/mix/lib/mix/tasks/compile.elixir.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ defmodule Mix.Tasks.Compile.Elixir do
2525
return a non-zero exit code
2626
* `--long-compilation-threshold N` - sets the "long compilation" threshold
2727
(in seconds) to `N` (see the docs for `Kernel.ParallelCompiler.files/2`)
28+
* `--all-warnings` - prints warnings even from files that do not need to be recompiled
2829
2930
## Configuration
3031
@@ -41,7 +42,8 @@ defmodule Mix.Tasks.Compile.Elixir do
4142

4243
@switches [force: :boolean, docs: :boolean, warnings_as_errors: :boolean,
4344
ignore_module_conflict: :boolean, debug_info: :boolean,
44-
verbose: :boolean, long_compilation_threshold: :integer]
45+
verbose: :boolean, long_compilation_threshold: :integer,
46+
all_warnings: :boolean]
4547

4648
@doc """
4749
Runs this task.

lib/mix/test/mix/tasks/compile.elixir_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,35 @@ defmodule Mix.Tasks.Compile.ElixirTest do
331331
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
332332
end
333333
end
334+
335+
test "prints warnings from non-stale files with --all-warnings" do
336+
in_fixture "no_mixfile", fn ->
337+
File.write!("lib/a.ex", """
338+
defmodule A do
339+
def my_fn(unused), do: :ok
340+
end
341+
""")
342+
343+
# First compilation should print unused variable warning
344+
import ExUnit.CaptureIO
345+
output = capture_io(:standard_error, fn ->
346+
Mix.Tasks.Compile.Elixir.run([]) == :ok
347+
end)
348+
349+
# Should also print warning
350+
assert capture_io(:standard_error, fn ->
351+
Mix.Tasks.Compile.Elixir.run(["--all-warnings"])
352+
end) == output
353+
354+
# Should not print warning once fixed
355+
File.write!("lib/a.ex", """
356+
defmodule A do
357+
def my_fn(_unused), do: :ok
358+
end
359+
""")
360+
assert capture_io(:standard_error, fn ->
361+
Mix.Tasks.Compile.Elixir.run(["--all-warnings"])
362+
end) == ""
363+
end
364+
end
334365
end

0 commit comments

Comments
 (0)