Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions lib/mix/lib/mix/task.compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ defmodule Mix.Task.Compiler do

* `:app` - app which modules have been compiled.

* `:build_scm` - the SCM module of the compiled project.
* `:scm` - the SCM module of the compiled project.

* `:modules_diff` - information about the compiled modules. The
value is a map with keys: `:added`, `:changed`, `:removed`,
Expand All @@ -68,6 +68,21 @@ defmodule Mix.Task.Compiler do
with `System.pid/0` to determine if compilation happened in
the same OS process as the listener.

* `{:dep_compiled, info}` - delivered after a dependency is compiled.
`info` is a map with the following keys:

* `:app` - the dependency app.

* `:scm` - the SCM module of the dependency.

* `:manager` - the dependency project management, possible values:
`:rebar3`, `:mix`, `:make`, `nil`.

* `:os_pid` - the operating system PID of the process that run
the compilation. The value is a string and it can be compared
with `System.pid/0` to determine if compilation happened in
the same OS process as the listener.

Note that the listener starts before any of the project apps are started.
"""

Expand Down Expand Up @@ -222,7 +237,7 @@ defmodule Mix.Task.Compiler do
lazy_message = fn ->
info = %{
app: config[:app],
build_scm: config[:build_scm],
scm: config[:build_scm],
modules_diff: lazy_modules_diff.(),
os_pid: System.pid()
}
Expand Down
17 changes: 17 additions & 0 deletions lib/mix/lib/mix/tasks/deps.compile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ defmodule Mix.Tasks.Deps.Compile do
false
end

if compiled? do
build_path = Mix.Project.build_path(config)

lazy_message = fn ->
info = %{
app: dep.app,
scm: dep.scm,
manager: dep.manager,
os_pid: System.pid()
}

{:dep_compiled, info}
end

Mix.Sync.PubSub.broadcast(build_path, lazy_message)
end

# We should touch fetchable dependencies even if they
# did not compile otherwise they will always be marked
# as stale, even when there is nothing to do.
Expand Down
13 changes: 13 additions & 0 deletions lib/mix/lib/mix/tasks/deps.loadpaths.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ defmodule Mix.Tasks.Deps.Loadpaths do

@impl true
def run(args) do
# Note that we need to ensure the dependencies are compiled first,
# before we can start the pub/sub listeners, since those come from
# the dependencies. Theoretically, between compiling dependencies
# and starting the listeners, there may be a concurrent compilation
# of the dependencies, which we would miss, and we would already
# have modules from our compilation loaded. To avoid this race
# condition we start the pub/sub beforehand and we accumulate all
# events until the listeners are started. Alternatively we could
# use a lock around compilation and sterning the listeners, however
# the added benefit of the current approach is that we consistently
# receive events for all dependency compilations. Also, if we ever
# decide to start the listeners later (e.g. after loadspaths), the
# accumulation approach still works.
Mix.PubSub.start()

if "--no-archives-check" not in args do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,34 @@ defmodule Reloader do
%{
modules_diff: %{added: added, changed: changed, removed: removed, timestamp: _timestamp},
app: app,
build_scm: build_scm,
scm: scm,
os_pid: os_pid
} = info

IO.write("""
Received :modules_compiled with
added: #{inspect(Enum.sort(added))}, changed: #{inspect(Enum.sort(changed))}, removed: #{inspect(Enum.sort(removed))}
app: #{inspect(app)}
build_scm: #{inspect(build_scm)}
scm: #{inspect(scm)}
os_pid: #{inspect(os_pid)}
""")

{:noreply, state}
end

def handle_info({:dep_compiled, info}, state) do
%{
app: app,
scm: scm,
manager: manager,
os_pid: os_pid
} = info

IO.write("""
Received :dep_compiled with
app: #{inspect(app)}
scm: #{inspect(scm)}
manager: #{inspect(manager)}
os_pid: #{inspect(os_pid)}
""")

Expand Down
78 changes: 51 additions & 27 deletions lib/mix/test/mix/tasks/compile_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -439,13 +439,13 @@ defmodule Mix.Tasks.CompileTest do
assert_receive {^port, {:data, "ok\n"}}, timeout
send(parent, :mix_started)

assert_receive {^port, {:data, output_erl}}, timeout
assert_receive {^port, {:data, output_ex}}, timeout
send(parent, {:output, output_erl <> output_ex})
send_port_outputs_to(port, parent)
end)

assert_receive :mix_started, timeout

# Project compilation

output = mix(["do", "compile", "+", "eval", "IO.write System.pid"])
os_pid = output |> String.split("\n") |> List.last()

Expand All @@ -455,12 +455,17 @@ defmodule Mix.Tasks.CompileTest do
Received :modules_compiled with
added: [:a, :b, :c], changed: [], removed: []
app: :with_reloader
build_scm: Mix.SCM.Path
scm: Mix.SCM.Path
os_pid: "#{os_pid}"
"""

assert_receive {:output, output}, timeout

assert output == """
Received :modules_compiled with
added: [A, B, C], changed: [], removed: []
app: :with_reloader
build_scm: Mix.SCM.Path
scm: Mix.SCM.Path
os_pid: "#{os_pid}"
"""

Expand All @@ -475,26 +480,7 @@ defmodule Mix.Tasks.CompileTest do
File.write!("lib/d.ex", "defmodule D do end")
File.write!("src/d.erl", "-module(d).")

spawn_link(fn ->
port =
mix_port([
"run",
"--no-halt",
"--no-compile",
"--no-start",
"--eval",
~s/IO.puts("ok"); IO.gets(""); System.halt()/
])

assert_receive {^port, {:data, "ok\n"}}, timeout
send(parent, :mix_started)

assert_receive {^port, {:data, output_erl}}, timeout
assert_receive {^port, {:data, output_ex}}, timeout
send(parent, {:output, output_erl <> output_ex})
end)

assert_receive :mix_started, timeout
# Project recompilation

output = mix(["do", "compile", "+", "eval", "IO.write System.pid"])
os_pid = output |> String.split("\n") |> List.last()
Expand All @@ -505,14 +491,52 @@ defmodule Mix.Tasks.CompileTest do
Received :modules_compiled with
added: [:d], changed: [:a], removed: [:b]
app: :with_reloader
build_scm: Mix.SCM.Path
scm: Mix.SCM.Path
os_pid: "#{os_pid}"
"""

assert_receive {:output, output}, timeout

assert output == """
Received :modules_compiled with
added: [D], changed: [A], removed: [B]
app: :with_reloader
build_scm: Mix.SCM.Path
scm: Mix.SCM.Path
os_pid: "#{os_pid}"
"""

# Dependency recompilation

output = mix(["do", "deps.compile", "--force", "+", "eval", "IO.write System.pid"])
os_pid = output |> String.split("\n") |> List.last()

assert_receive {:output, output}, timeout

assert output == """
Received :modules_compiled with
added: [Reloader], changed: [], removed: []
app: :reloader
scm: Mix.SCM.Path
os_pid: "#{os_pid}"
"""

assert_receive {:output, output}, timeout

assert output == """
Received :dep_compiled with
app: :reloader
scm: Mix.SCM.Path
manager: :mix
os_pid: "#{os_pid}"
"""
end)
end

defp send_port_outputs_to(port, pid) do
receive do
{^port, {:data, output}} ->
send(pid, {:output, output})
send_port_outputs_to(port, pid)
end
end
end
Loading