Skip to content

Commit 13a07cc

Browse files
committed
Add depends_on/0 callback to Task behaviour
1 parent c1c2cf8 commit 13a07cc

File tree

6 files changed

+100
-4
lines changed

6 files changed

+100
-4
lines changed

lib/mix/lib/mix/task.compiler.ex

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,18 @@ defmodule Mix.Task.Compiler do
175175
"""
176176
@callback clean() :: any
177177

178-
@optional_callbacks clean: 0, manifests: 0, diagnostics: 0
178+
@doc """
179+
A compiler task (as well as a regular task) may return a list of
180+
dependent tasks if they are to be re-enabled when this task gets
181+
re-enabled.
182+
183+
For instance, if task `"foo"` executes `"bar"` with
184+
`Mix.Task.run("bar")` from `run/1`, this method should be
185+
implemented to return `["bar"]`.
186+
"""
187+
@callback depends_on :: [binary]
188+
189+
@optional_callbacks clean: 0, manifests: 0, depends_on: 0
179190

180191
@doc """
181192
Adds a callback that runs after a given compiler.

lib/mix/lib/mix/task.ex

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ defmodule Mix.Task do
131131
"""
132132
@callback run(command_line_args :: [binary]) :: any
133133

134+
@doc """
135+
A task may return a list of dependent tasks
136+
if they are to be re-enabled when this task gets re-enabled.
137+
138+
For instance, if task `"foo"` executes `"bar"` with
139+
`Mix.Task.run("bar")` from `run/1`, this method should be implemented
140+
to return `["bar"]`.
141+
"""
142+
@callback depends_on :: [binary]
143+
144+
@optional_callbacks depends_on: 0
145+
134146
@doc false
135147
defmacro __using__(_opts) do
136148
quote do
@@ -613,10 +625,25 @@ defmodule Mix.Task do
613625
child projects.
614626
"""
615627
@spec reenable(task_name) :: :ok
616-
def reenable(task) when is_binary(task) or is_atom(task) do
617-
task = to_string(task)
628+
def reenable(task) when is_atom(task) do
629+
task |> to_string() |> reenable()
630+
end
631+
632+
def reenable(task) when is_binary(task) do
633+
module = get(task)
634+
635+
# it’s safe against `no_return` because otherwise `run/1`
636+
# would be `no_return` as well
637+
if module && function_exported?(module, :depends_on, 0) do
638+
Enum.each(module.depends_on(), &reenable/1)
639+
end
640+
641+
do_reenable(task, module)
642+
end
643+
644+
defp do_reenable(task, module) do
618645
proj = Mix.Project.get()
619-
recursive = (module = get(task)) && recursive(module)
646+
recursive = module && recursive(module)
620647

621648
Mix.TasksServer.delete_many([{:task, task, proj}, {:alias, task, proj}])
622649

lib/mix/lib/mix/tasks/compile.all.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ defmodule Mix.Tasks.Compile.All do
8989
result
9090
end
9191

92+
@impl true
93+
@doc false
94+
def depends_on do
95+
Mix.Project.config()
96+
|> Mix.Tasks.Compile.compilers()
97+
|> Enum.map(&"compile.#{&1}")
98+
end
99+
92100
defp compile([], _, status, diagnostics) do
93101
{status, diagnostics}
94102
end

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ defmodule Mix.Tasks.Compile do
174174
{res, diagnostics}
175175
end
176176

177+
@impl true
178+
def depends_on do
179+
["loadpaths", "compile.all"]
180+
end
181+
177182
defp format(expression, args) do
178183
:io_lib.format(expression, args) |> IO.iodata_to_binary()
179184
end

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,33 @@ defmodule Mix.Tasks.Compile.ElixirTest do
855855
end)
856856
end
857857

858+
test "recompiles newly created files" do
859+
in_fixture("no_mixfile", fn ->
860+
Mix.Project.push(MixTest.Case.Sample)
861+
862+
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
863+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
864+
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
865+
866+
Mix.shell().flush()
867+
868+
File.write!("lib/z.ex", """
869+
defmodule Z do
870+
def ok, do: :ok
871+
end
872+
""")
873+
874+
Mix.Task.reenable("compile.elixir")
875+
assert {:ok, []} = Mix.Tasks.Compile.Elixir.run(["--verbose"])
876+
877+
assert_received {:mix_shell, :info, ["Compiled lib/z.ex"]}
878+
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
879+
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
880+
881+
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.Z.beam")
882+
end)
883+
end
884+
858885
test "recompiles dependent changed modules" do
859886
in_fixture("no_mixfile", fn ->
860887
Mix.Project.push(MixTest.Case.Sample)

lib/mix/test/mix/tasks/compile_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,24 @@ defmodule Mix.Tasks.CompileTest do
112112
end)
113113
end
114114

115+
test "recompiles newly created files" do
116+
in_fixture("no_mixfile", fn ->
117+
assert Mix.Tasks.Compile.run(["--verbose"]) == {:ok, []}
118+
Mix.shell().flush()
119+
120+
File.write!("lib/z.ex", """
121+
defmodule Z do
122+
def ok, do: :ok
123+
end
124+
""")
125+
126+
Mix.Task.reenable("compile")
127+
assert {:ok, []} = Mix.Task.run("compile")
128+
129+
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.Z.beam")
130+
end)
131+
end
132+
115133
test "recompiles app cache if manifest changes" do
116134
in_fixture("no_mixfile", fn ->
117135
Mix.Tasks.Compile.run(["--force"])

0 commit comments

Comments
 (0)