Skip to content

Commit 7fa1914

Browse files
committed
Store the lock in the manifest
This is to address an issue where a _build restored for another version of a dependency would come up as valid.
1 parent aef3dce commit 7fa1914

File tree

10 files changed

+70
-48
lines changed

10 files changed

+70
-48
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,8 @@ defmodule Mix.Compilers.Elixir do
965965
# Since Elixir is a dependency itself, we need to touch the lock
966966
# so the current Elixir version, used to compile the files above,
967967
# is properly stored.
968-
Mix.Dep.ElixirSCM.update(Mix.Project.config()[:build_scm])
968+
config = Mix.Project.config()
969+
Mix.Dep.ElixirSCM.update(config[:build_scm], config[:deps_lock])
969970
:ok
970971
end
971972

lib/mix/lib/mix/dep.ex

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,18 @@ defmodule Mix.Dep do
258258
|> Keyword.put(:build_scm, scm)
259259
|> Keyword.put(:deps_app_path, opts[:build])
260260

261-
# If the dependency is not fetchable, then it is never compiled
262-
# from scratch and therefore it needs the parent configuration
263-
# files to know when to recompile.
264-
config = [inherit_parent_config_files: not scm.fetchable?()] ++ config
261+
config =
262+
if scm.fetchable?() do
263+
# If the dependency is fetchable, we want to store the lock in
264+
# the Elixir SCM file.
265+
[deps_lock: opts[:lock]] ++ config
266+
else
267+
# If the dependency is not fetchable, then it is never compiled
268+
# from scratch and therefore it needs the parent configuration
269+
# files to know when to recompile.
270+
[inherit_parent_config_files: true] ++ config
271+
end
272+
265273
env = opts[:env] || :prod
266274
old_env = Mix.env()
267275

@@ -436,23 +444,35 @@ defmodule Mix.Dep do
436444
%{dep | status: :lockoutdated}
437445

438446
:ok ->
439-
check_manifest(dep, opts[:build])
447+
check_manifest(dep)
440448
end
441449
else
442450
dep
443451
end
444452
end
445453

446-
defp check_manifest(%{scm: scm} = dep, build_path) do
454+
defp check_manifest(%{scm: scm, opts: opts} = dep) do
447455
vsn = {System.version(), :erlang.system_info(:otp_release)}
456+
lock = opts[:lock]
448457

449-
case Mix.Dep.ElixirSCM.read(Path.join(build_path, ".mix")) do
450-
{:ok, old_vsn, _} when old_vsn != vsn ->
458+
case Mix.Dep.ElixirSCM.read(Path.join(opts[:build], ".mix")) do
459+
{:ok, old_vsn, _, _} when old_vsn != vsn ->
451460
%{dep | status: {:vsnlock, old_vsn}}
452461

453-
{:ok, _, old_scm} when old_scm != scm ->
462+
{:ok, _, old_scm, _} when old_scm != scm ->
454463
%{dep | status: {:scmlock, old_scm}}
455464

465+
{:ok, _, _, old_lock} when old_lock != lock ->
466+
%{dep | status: :compile}
467+
468+
:error ->
469+
if scm.fetchable?() do
470+
%{dep | status: :compile}
471+
else
472+
dep
473+
end
474+
475+
# If the file is missing, it is handled in the loader
456476
_ ->
457477
dep
458478
end

lib/mix/lib/mix/dep/elixir_scm.ex

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@
33
# SPDX-FileCopyrightText: 2012 Plataformatec
44

55
# Manifest file where we treat Erlang/OTP, Elixir and SCMs as a dependency.
6+
# We also store the exact version this dependency was compiled for.
67
defmodule Mix.Dep.ElixirSCM do
78
@moduledoc false
89
@manifest "compile.elixir_scm"
9-
@manifest_vsn 1
10+
@manifest_vsn 2
1011

1112
def manifest(manifest_path \\ Mix.Project.manifest_path()) do
1213
Path.join(manifest_path, @manifest)
1314
end
1415

15-
def update(manifest_path \\ Mix.Project.manifest_path(), scm) do
16+
def update(manifest_path \\ Mix.Project.manifest_path(), scm, lock) do
1617
File.mkdir_p!(manifest_path)
1718

1819
manifest_data =
19-
{@manifest_vsn, {System.version(), :erlang.system_info(:otp_release)}, scm}
20+
{@manifest_vsn, {System.version(), :erlang.system_info(:otp_release)}, scm, lock}
2021
|> :erlang.term_to_binary()
2122

2223
File.write!(manifest(manifest_path), manifest_data)
@@ -26,10 +27,10 @@ defmodule Mix.Dep.ElixirSCM do
2627
case File.read(manifest(manifest_path)) do
2728
{:ok, contents} ->
2829
try do
29-
{@manifest_vsn, vsn, scm} = :erlang.binary_to_term(contents)
30-
{:ok, vsn, scm}
30+
{@manifest_vsn, vsn, scm, lock} = :erlang.binary_to_term(contents)
31+
{:ok, vsn, scm, lock}
3132
rescue
32-
_ -> {:ok, {"1.0.0", ~c"17"}, nil}
33+
_ -> {:ok, {"1.0.0", ~c"17"}, nil, nil}
3334
end
3435

3536
_ ->

lib/mix/lib/mix/dep/fetcher.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ defmodule Mix.Dep.Fetcher do
6363

6464
# If the dependency is not available or we have a lock mismatch
6565
out_of_date?(dep) ->
66-
# Mark the dependency as fetched upfront, in case updating
67-
# fails, get interrupted, or corrupted.
66+
# Mark the dependency as fetched upfront, in case updating fails,
67+
# gets interrupted, or corrupted.
6868
mark_as_fetched([dep])
6969

7070
new =

lib/mix/lib/mix/dep/loader.ex

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,6 @@ defmodule Mix.Dep.Loader do
389389
not ok?(dep) ->
390390
dep
391391

392-
recently_fetched?(dep) ->
393-
%{dep | status: :compile}
394-
395392
opts_app == false ->
396393
dep
397394

@@ -406,10 +403,6 @@ defmodule Mix.Dep.Loader do
406403
end
407404
end
408405

409-
defp recently_fetched?(%Mix.Dep{opts: opts, scm: scm}) do
410-
scm.fetchable?() and not File.exists?(Path.join(opts[:build], ".mix/compile.elixir_scm"))
411-
end
412-
413406
defp app_status(app_path, app, req) do
414407
case Mix.AppLoader.read_app(app, app_path) do
415408
{:ok, properties} ->

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ defmodule Mix.Tasks.Compile.App do
188188
|> add_modules(modules, compile_path)
189189

190190
contents = to_erl_term({:application, app, properties})
191+
:application.unload(app)
191192
:application.load({:application, app, properties})
192193

193194
Mix.Project.ensure_structure()

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,18 @@ defmodule Mix.Tasks.Deps.Compile do
104104

105105
# If all dependencies are local and ok, do not bother starting
106106
# processes as it will likely slow everything down.
107-
compiled? =
108-
if count > 1 and match?([_, _ | _], deps) and
109-
not Enum.all?(deps, &(Mix.Dep.ok?(&1) and not &1.scm.fetchable?())) do
110-
Mix.shell().info("mix deps.compile running across #{count} OS processes")
111-
Mix.Tasks.Deps.Partition.server(deps, count, force?)
112-
else
113-
config = Mix.Project.deps_config()
114-
true in Enum.map(deps, &compile_single(&1, force?, config))
115-
end
107+
if count > 1 and match?([_, _ | _], deps) and
108+
not Enum.all?(deps, &(Mix.Dep.ok?(&1) and not &1.scm.fetchable?())) do
109+
Mix.shell().info("mix deps.compile running across #{count} OS processes")
116110

117-
if compiled?, do: Mix.Task.run("will_recompile"), else: :ok
111+
if Mix.Tasks.Deps.Partition.server(deps, count, force?) do
112+
# Each partition will trigger will_recompile but we need to trigger it here too
113+
Mix.Task.run("will_recompile")
114+
end
115+
else
116+
config = Mix.Project.deps_config()
117+
Enum.each(deps, &compile_single(&1, force?, config))
118+
end
118119
end
119120

120121
@doc false
@@ -169,8 +170,14 @@ defmodule Mix.Tasks.Deps.Compile do
169170
# We should touch fetchable dependencies even if they
170171
# did not compile otherwise they will always be marked
171172
# as stale, even when there is nothing to do.
172-
fetchable? = touch_fetchable(scm, opts[:build])
173-
compiled? and fetchable?
173+
fetchable? = touch_fetchable(scm, opts)
174+
175+
if compiled? and fetchable? do
176+
Mix.Task.run("will_recompile")
177+
true
178+
else
179+
false
180+
end
174181
end
175182

176183
defp check_unavailable!(app, scm, {:unavailable, path}) do
@@ -192,9 +199,9 @@ defmodule Mix.Tasks.Deps.Compile do
192199
:ok
193200
end
194201

195-
defp touch_fetchable(scm, path) do
202+
defp touch_fetchable(scm, opts) do
196203
if scm.fetchable?() do
197-
Mix.Dep.ElixirSCM.update(Path.join(path, ".mix"), scm)
204+
Mix.Dep.ElixirSCM.update(Path.join(opts[:build], ".mix"), scm, opts[:lock])
198205
true
199206
else
200207
false

lib/mix/lib/mix/tasks/loadpaths.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ defmodule Mix.Tasks.Loadpaths do
5959
# any of SCM or Elixir version changes. Applies
6060
# to dependencies and the main project alike.
6161
case Mix.Dep.ElixirSCM.read() do
62-
{:ok, old_vsn, old_scm} when old_vsn != vsn or old_scm != scm ->
62+
{:ok, old_vsn, old_scm, _deps_lock} when old_vsn != vsn or old_scm != scm ->
6363
if Mix.debug?() do
6464
Mix.shell().info(
6565
"-- Recompiling #{inspect(config[:app])} because Erlang/OTP, Elixir, or SCM version changed"

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
608608

609609
assert File.exists?("_build/dev/lib/sample")
610610
assert File.exists?("_build/dev/lib/sample/consolidated")
611-
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path}
611+
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path, nil}
612612

613613
Mix.Task.clear()
614614
File.write!("_build/dev/lib/sample/consolidated/.to_be_removed", "")
@@ -617,7 +617,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
617617
File.touch!("_build/dev/lib/sample/.mix/compile.elixir_scm", @old_time)
618618

619619
Mix.Tasks.Compile.run([])
620-
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path}
620+
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path, nil}
621621

622622
assert mtime("_build/dev/lib/sample/.mix/compile.elixir_scm") > @old_time
623623
refute File.exists?("_build/dev/lib/sample/consolidated/.to_be_removed")
@@ -631,15 +631,15 @@ defmodule Mix.Tasks.Compile.ElixirTest do
631631
purge([A, B])
632632

633633
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
634-
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path}
634+
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path, nil}
635635

636636
Mix.Task.clear()
637637
manifest_data = :erlang.term_to_binary({1, @elixir_otp_version, :another})
638638
File.write!("_build/dev/lib/sample/.mix/compile.elixir_scm", manifest_data)
639639
File.touch!("_build/dev/lib/sample/.mix/compile.elixir_scm", @old_time)
640640

641641
Mix.Tasks.Compile.run([])
642-
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path}
642+
assert Mix.Dep.ElixirSCM.read() == {:ok, @elixir_otp_version, Mix.SCM.Path, nil}
643643

644644
assert mtime("_build/dev/lib/sample/.mix/compile.elixir_scm") > @old_time
645645
end)
@@ -704,9 +704,6 @@ defmodule Mix.Tasks.Compile.ElixirTest do
704704
Mix.State.clear_cache()
705705
purge([GitRepo.MixProject, PathOnGitRepo.MixProject, PathOnGitRepo.Hello])
706706

707-
# Unload the git repo application so we can pick the new modules definition
708-
:ok = Application.unload(:git_repo)
709-
710707
Mix.Tasks.Deps.Update.run(["--all"])
711708
assert File.read!("mix.lock") =~ last
712709

lib/mix/test/mix/tasks/deps_test.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,9 @@ defmodule Mix.Tasks.DepsTest do
764764
File.mkdir_p!("_build/dev/lib/ok/.mix")
765765

766766
manifest_data =
767-
:erlang.term_to_binary({1, {System.version(), :erlang.system_info(:otp_release)}, :scm})
767+
:erlang.term_to_binary(
768+
{2, {System.version(), :erlang.system_info(:otp_release)}, :scm, nil}
769+
)
768770

769771
File.write!("_build/dev/lib/ok/.mix/compile.elixir_scm", manifest_data)
770772
Mix.Task.clear()

0 commit comments

Comments
 (0)