Skip to content

Commit 3452e6a

Browse files
committed
Ensure compilation of sibling deps do not mark path deps as changed, closes #15158
1 parent ababaf8 commit 3452e6a

5 files changed

Lines changed: 121 additions & 35 deletions

File tree

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,10 @@ defmodule Mix.Compilers.Elixir do
180180
Mix.Compilers.Protocol.status(config_mtime > old_config_mtime, opts)
181181
end
182182

183-
if stale != [] or stale_modules != %{} or removed != [] or deps_changed? or
184-
consolidation_status == :force do
183+
consolidate? =
184+
consolidation_status == :force or (deps_changed? and consolidation_status != :off)
185+
186+
if stale != [] or stale_modules != %{} or removed != [] or consolidate? do
185187
path = opts[:purge_consolidation_path_if_stale]
186188

187189
if is_binary(path) and Code.delete_path(path) do
@@ -266,6 +268,25 @@ defmodule Mix.Compilers.Elixir do
266268
Code.compiler_options(previous_opts)
267269
end
268270
else
271+
# Only the dependencies changed but nothing was recompiled,
272+
# so we need to update the config_mtime configuration but
273+
# keep its original timestamp so recompilation does not cascade.
274+
if deps_changed? do
275+
write_manifest(
276+
manifest,
277+
all_modules,
278+
all_sources,
279+
all_local_exports,
280+
old_parents,
281+
old_cache_key,
282+
old_deps_config,
283+
old_project_mtime,
284+
config_mtime,
285+
old_protocols_and_impls,
286+
modified
287+
)
288+
end
289+
269290
all_warnings = Keyword.get(opts, :all_warnings, true)
270291
previous_warnings = previous_warnings(sources, all_warnings)
271292
unless_warnings_as_errors(opts, {:noop, previous_warnings})
@@ -276,7 +297,10 @@ defmodule Mix.Compilers.Elixir do
276297
# If you change this config, you need to bump @manifest_vsn
277298
%{
278299
local: Enum.sort(Enum.map(local_deps, &{&1.app, true})),
279-
lock: Enum.sort(Mix.Dep.Lock.read()),
300+
lock:
301+
Mix.Dep.Lock.read()
302+
|> Map.take(Mix.Project.deps_apps())
303+
|> Enum.sort(),
280304
config: Enum.sort(Mix.Tasks.Loadconfig.read_compile()),
281305
dbg: Application.fetch_env!(:elixir, :dbg_callback)
282306
}

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,18 +201,23 @@ defmodule Mix.Tasks.Compile.App do
201201
:application.unload(app)
202202
:application.load({:application, app, properties})
203203

204-
Mix.Project.ensure_structure()
205-
File.write!(target, IO.chardata_to_string([contents, ?.]))
206-
File.touch!(target, new_mtime)
207-
208-
# If we just created the .app file, it will have touched
209-
# the directory mtime, so we need to reset it.
210-
if current_properties == [] do
211-
File.touch!(compile_path, new_mtime)
212-
end
204+
if opts[:force] || Map.new(current_properties) != Map.new(properties) do
205+
Mix.Project.ensure_structure()
206+
File.write!(target, IO.chardata_to_string([contents, ?.]))
207+
File.touch!(target, new_mtime)
208+
209+
# If we just created the .app file, it will have touched
210+
# the directory mtime, so we need to reset it.
211+
if current_properties == [] do
212+
File.touch!(compile_path, new_mtime)
213+
end
213214

214-
Mix.shell().info("Generated #{app} app")
215-
{:ok, []}
215+
Mix.shell().info("Generated #{app} app")
216+
{:ok, []}
217+
else
218+
File.touch!(target, new_mtime)
219+
{:noop, []}
220+
end
216221
else
217222
:application.load({:application, app, current_properties})
218223
{:noop, []}

lib/mix/lib/mix/tasks/test.coverage.ex

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,6 @@ defmodule Mix.Tasks.Test.Coverage do
7575
coverage techniques. It is up to you and your team to specify how much
7676
emphasis you want to place on it.
7777
78-
## Native coverage
79-
80-
Erlang/OTP 27+ supports "native coverage", which relies on the JIT compiler
81-
to compute coverage, leading to better performance when running a test suite
82-
with coverage enabled.
83-
84-
You can enable it by running:
85-
86-
ERL_COMPILER_OPTIONS=line_coverage mix test
87-
8878
## Exporting coverage
8979
9080
This task can be used when you need to group the coverage

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ defmodule Mix.Tasks.Compile.AppTest do
9191

9292
# No-op with untouched unset compile_env
9393
reset_config.()
94-
assert Mix.Tasks.Compile.App.run([]) == {:ok, []}
94+
assert Mix.Tasks.Compile.App.run([]) == {:noop, []}
9595
assert parse_resource_file(:sample)[:compile_env] == [{:app, :key, :error}]
9696

9797
# Recompiles with new compile_env

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

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -523,15 +523,21 @@ defmodule Mix.Tasks.Compile.ElixirTest do
523523

524524
test "recompiles files when lock changes" do
525525
in_fixture("no_mixfile", fn ->
526+
Mix.ProjectStack.post_config(
527+
deps: [{:git_repo, git: MixTest.Case.fixture_path("git_repo")}]
528+
)
529+
526530
Mix.Project.push(MixTest.Case.Sample)
527-
Process.put({MixTest.Case.Sample, :application}, extra_applications: [:logger])
528531

529532
File.write!("lib/a.ex", """
530533
defmodule A do
531-
_ = Logger.metadata()
534+
_ = GitRepo.__info__(:module)
532535
end
533536
""")
534537

538+
Mix.Task.run("deps.get")
539+
Mix.Task.run("loadpaths")
540+
535541
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
536542
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
537543
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
@@ -548,7 +554,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
548554

549555
# Adding to lock recompiles
550556
File.write!("mix.lock", """
551-
%{"logger": :unused}
557+
%{"git_repo": :unused}
552558
""")
553559

554560
assert recompile.() == {:ok, []}
@@ -558,32 +564,32 @@ defmodule Mix.Tasks.Compile.ElixirTest do
558564

559565
# Changing lock recompiles
560566
File.write!("mix.lock", """
561-
%{"logger": :another}
567+
%{"git_repo": :another}
562568
""")
563569

564570
assert recompile.() == {:ok, []}
565571
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
566572
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
567573
assert mtime("_build/dev/lib/sample/.mix/compile.elixir") > @old_time
568574

569-
# Removing a lock recompiles
575+
# Adding to the lock does not recompile
570576
File.write!("mix.lock", """
571-
%{}
577+
%{"git_repo": :another, "unknown": :unknown}
572578
""")
573579

574580
assert recompile.() == {:ok, []}
575-
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
581+
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
576582
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
577-
assert mtime("_build/dev/lib/sample/.mix/compile.elixir") > @old_time
578583

579-
# Adding an unknown dependency returns :ok but does not recompile
584+
# Removing recompiles
580585
File.write!("mix.lock", """
581586
%{"unknown": :unknown}
582587
""")
583588

584589
assert recompile.() == {:ok, []}
585-
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
590+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
586591
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
592+
assert mtime("_build/dev/lib/sample/.mix/compile.elixir") > @old_time
587593
end)
588594
end
589595

@@ -751,6 +757,67 @@ defmodule Mix.Tasks.Compile.ElixirTest do
751757
purge([GitRepo, GitRepo.MixProject])
752758
end
753759

760+
test "does not recompile files from path dependencies when non-child dependency change" do
761+
# Get Git repo first revision
762+
[last, first | _] = get_git_repo_revs("git_repo")
763+
764+
in_fixture("no_mixfile", fn ->
765+
File.mkdir_p!("path_dep/lib")
766+
767+
File.write!("path_dep/mix.exs", """
768+
defmodule PathDep.MixProject do
769+
use Mix.Project
770+
771+
def project do
772+
[
773+
app: :path_dep,
774+
version: "0.1.0",
775+
deps: []
776+
]
777+
end
778+
end
779+
""")
780+
781+
File.write!("path_dep/lib/path_dep_hello.ex", """
782+
defmodule PathDep.Hello do
783+
end
784+
""")
785+
786+
File.write!("lib/a.ex", """
787+
defmodule A do
788+
PathDep.Hello.__info__(:module)
789+
end
790+
""")
791+
792+
File.write!("mix.lock", inspect(%{git_repo: {:git, fixture_path("git_repo"), first, []}}))
793+
794+
Mix.ProjectStack.post_config(
795+
deps: [
796+
{:git_repo, "0.1.0", git: fixture_path("git_repo")},
797+
{:path_dep, path: "path_dep"}
798+
]
799+
)
800+
801+
Mix.Project.push(MixTest.Case.Sample)
802+
Mix.Task.run("deps.get")
803+
Mix.Task.run("compile", ["--verbose"])
804+
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
805+
806+
Mix.Task.clear()
807+
Mix.State.clear_cache()
808+
purge([GitRepo.MixProject, PathDep.MixProject])
809+
810+
ensure_touched("mix.lock", "_build/dev/lib/sample/.mix/compile.lock")
811+
Mix.Task.run("deps.update", ["--all"])
812+
assert File.read!("mix.lock") =~ last
813+
814+
Mix.Task.run("compile", ["--verbose"])
815+
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
816+
end)
817+
after
818+
purge([GitRepo, GitRepo.MixProject, PathDep.MixProject])
819+
end
820+
754821
test "does not write BEAM files down on failures" do
755822
in_tmp("blank", fn ->
756823
Mix.Project.push(MixTest.Case.Sample)

0 commit comments

Comments
 (0)