Skip to content

Commit 2f2f2e0

Browse files
author
José Valim
committed
Dependencies convergence properly relies on requirements
Previously Mix required dependencies to have exactly the same requirements but now we just check all requirements satisfy the current version.
1 parent d20190c commit 2f2f2e0

File tree

5 files changed

+108
-41
lines changed

5 files changed

+108
-41
lines changed

lib/mix/lib/mix/deps.ex

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,24 @@ defmodule Mix.Deps do
221221
def format_status(Mix.Dep[status: :nolock]),
222222
do: "the dependency is not locked"
223223

224+
def format_status(Mix.Dep[app: app, status: { :divergedreq, other }] = dep) do
225+
"the dependency #{app} defined\n" <>
226+
"#{dep_status(dep)}" <>
227+
"\n does not match the requirement specified\n" <>
228+
"#{dep_status(other)}" <>
229+
"\n Ensure they match or specify one of the above in your #{inspect Mix.Project.get} deps and set `override: true`"
230+
end
231+
224232
def format_status(Mix.Dep[app: app, status: { :diverged, other }] = dep) do
225233
"different specs were given for the #{inspect app} app:\n" <>
226234
"#{dep_status(dep)}#{dep_status(other)}" <>
227-
"\n Ensure they match or specify one in your #{inspect Mix.Project.get} deps and set `override: true`"
235+
"\n Ensure they match or specify one of the above in your #{inspect Mix.Project.get} deps and set `override: true`"
228236
end
229237

230238
def format_status(Mix.Dep[app: app, status: { :overriden, other }] = dep) do
231239
"the dependency #{app} in #{Path.relative_to_cwd(dep.from)} is overriding a child dependency:\n" <>
232240
"#{dep_status(dep)}#{dep_status(other)}" <>
233-
"\n Ensure they match or specify one in your #{inspect Mix.Project.get} deps and set `override: true`"
241+
"\n Ensure they match or specify one of the above in your #{inspect Mix.Project.get} deps and set `override: true`"
234242
end
235243

236244
def format_status(Mix.Dep[status: { :unavailable, _ }]),
@@ -280,6 +288,7 @@ defmodule Mix.Deps do
280288
"""
281289
def available?(Mix.Dep[status: { :overriden, _ }]), do: false
282290
def available?(Mix.Dep[status: { :diverged, _ }]), do: false
291+
def available?(Mix.Dep[status: { :divergedreq, _ }]), do: false
283292
def available?(Mix.Dep[status: { :elixirreq, _ }]), do: false
284293
def available?(Mix.Dep[status: { :unavailable, _ }]), do: false
285294
def available?(Mix.Dep[]), do: true

lib/mix/lib/mix/deps/converger.ex

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ defmodule Mix.Deps.Converger do
132132
Mix.Dep[app: other_app, opts: other_opts] = other
133133

134134
cond do
135-
app == other_app && (other_opts[:override] || converge?(dep, other)) ->
136-
{ other, acc }
135+
app == other_app && (other_opts[:override] || converge?(other, dep)) ->
136+
{ with_matching_req(other, dep), acc }
137137
app == other_app ->
138138
{ other.status({ :overriden, dep }), acc }
139139
true ->
@@ -159,8 +159,8 @@ defmodule Mix.Deps.Converger do
159159
cond do
160160
app != other_app ->
161161
{ other, match }
162-
converge?(dep, other) ->
163-
{ other, true }
162+
converge?(other, dep) ->
163+
{ with_matching_req(other, dep), true }
164164
true ->
165165
{ other.status({ :diverged, dep }), true }
166166
end
@@ -169,10 +169,22 @@ defmodule Mix.Deps.Converger do
169169
if match, do: acc
170170
end
171171

172-
defp converge?(Mix.Dep[scm: scm, requirement: req, opts: opts1],
173-
Mix.Dep[scm: scm, requirement: req, opts: opts2]) do
172+
defp converge?(Mix.Dep[scm: scm, opts: opts1], Mix.Dep[scm: scm, opts: opts2]) do
174173
scm.equal?(opts1, opts2)
175174
end
176175

177176
defp converge?(_, _), do: false
177+
178+
def with_matching_req(Mix.Dep[] = other, Mix.Dep[] = dep) do
179+
case other.status do
180+
{ :ok, vsn } when not nil?(vsn) ->
181+
if Mix.Deps.Retriever.vsn_match?(dep.requirement, vsn) do
182+
other
183+
else
184+
other.status({ :divergedreq, dep })
185+
end
186+
_ ->
187+
other
188+
end
189+
end
178190
end

lib/mix/lib/mix/deps/retriever.ex

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ defmodule Mix.Deps.Retriever do
4646
end)
4747
end
4848

49+
@doc """
50+
Checks if a requirement from a dependency matches
51+
the given version.
52+
"""
53+
def vsn_match?(nil, _actual), do: true
54+
def vsn_match?(req, actual) when is_regex(req), do: actual =~ req
55+
def vsn_match?(req, actual) when is_binary(req) do
56+
Version.match?(actual, req)
57+
end
58+
4959
## Helpers
5060

5161
defp to_dep(tuple, scms, from, manager // nil) do
@@ -193,12 +203,6 @@ defmodule Mix.Deps.Retriever do
193203
end
194204
end
195205

196-
defp vsn_match?(nil, _actual), do: true
197-
defp vsn_match?(req, actual) when is_regex(req), do: actual =~ req
198-
defp vsn_match?(req, actual) when is_binary(req) do
199-
Version.match?(actual, req)
200-
end
201-
202206
defp old_elixir_lock do
203207
old_vsn = Mix.Deps.Lock.elixir_vsn
204208
if old_vsn && old_vsn != System.version do

lib/mix/test/mix/deps_test.exs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,28 @@ defmodule Mix.DepsTest do
8282
Mix.Project.pop
8383
end
8484

85+
defmodule NestedDepsApp do
86+
def project do
87+
[
88+
app: :raw_sample,
89+
version: "0.1.0",
90+
deps: [
91+
{ :deps_repo, "0.1.0", path: "custom/deps_repo" }
92+
]
93+
]
94+
end
95+
end
96+
97+
test "nested deps come first" do
98+
Mix.Project.push NestedDepsApp
99+
100+
in_fixture "deps_status", fn ->
101+
assert Enum.map(Mix.Deps.fetched, &(&1.app)) == [:git_repo, :deps_repo]
102+
end
103+
after
104+
Mix.Project.pop
105+
end
106+
85107
defmodule ConvergedDepsApp do
86108
def project do
87109
[
@@ -95,11 +117,11 @@ defmodule Mix.DepsTest do
95117
end
96118
end
97119

98-
test "correctly order overriden deps" do
120+
test "correctly order converged deps" do
99121
Mix.Project.push ConvergedDepsApp
100122

101123
in_fixture "deps_status", fn ->
102-
assert [:git_repo, :deps_repo] == Enum.map(Mix.Deps.fetched, &(&1.app))
124+
assert Enum.map(Mix.Deps.fetched, &(&1.app)) == [:git_repo, :deps_repo]
103125
end
104126
after
105127
Mix.Project.pop

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ defmodule Mix.Tasks.DepsTest do
142142
Mix.Project.pop
143143
end
144144

145-
test "check list of dependencies and their status with failure" do
145+
test "check slist of dependencies and their status with failure" do
146146
Mix.Project.push OutOfDateDepsApp
147147

148148
in_fixture "deps_status", fn ->
@@ -156,7 +156,7 @@ defmodule Mix.Tasks.DepsTest do
156156
Mix.Project.pop
157157
end
158158

159-
test "check list of dependencies and their status on failure" do
159+
test "checks list of dependencies and their status on failure" do
160160
Mix.Project.push DepsApp
161161

162162
in_fixture "deps_status", fn ->
@@ -228,7 +228,7 @@ defmodule Mix.Tasks.DepsTest do
228228
end
229229
end
230230

231-
test "by default sets deps env to prod" do
231+
test "sets deps env to prod by default" do
232232
Mix.Project.push DepsEnvApp
233233

234234
in_fixture "deps_status", fn ->
@@ -284,7 +284,7 @@ defmodule Mix.Tasks.DepsTest do
284284
version: "0.1.0",
285285
deps: [
286286
{ :deps_repo, "0.1.0", path: "custom/deps_repo" },
287-
{ :git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo") }
287+
{ :git_repo, ">= 0.1", git: MixTest.Case.fixture_path("git_repo") }
288288
]
289289
]
290290
end
@@ -345,10 +345,6 @@ defmodule Mix.Tasks.DepsTest do
345345
assert_received { :mix_shell, :info, [^message] }
346346
refute_received { :mix_shell, :info, ["* Compiling deps_repo"] }
347347
assert_received { :mix_shell, :info, ["Generated git_repo.app"] }
348-
349-
Mix.Tasks.Deps.Update.run ["--all"]
350-
assert_received { :mix_shell, :info, ["* Updating deps_repo (custom/deps_repo)"] }
351-
refute_received { :mix_shell, :info, ["* Compiling deps_repo"] }
352348
end
353349
after
354350
Mix.Project.pop
@@ -374,6 +370,42 @@ defmodule Mix.Tasks.DepsTest do
374370
Mix.Project.pop
375371
end
376372

373+
test "fails on diverged dependencies by requirement" do
374+
Mix.Project.push ConvergedDepsApp
375+
376+
in_fixture "deps_status", fn ->
377+
File.write!("custom/deps_repo/mix.exs", """)
378+
defmodule DepsRepo do
379+
use Mix.Project
380+
381+
def project do
382+
[
383+
app: :deps_repo,
384+
version: "0.1.0",
385+
deps: [
386+
{ :git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo") }
387+
]
388+
]
389+
end
390+
end
391+
"""
392+
393+
assert_raise Mix.Error, fn ->
394+
Mix.Tasks.Deps.Get.run []
395+
end
396+
397+
receive do
398+
{ :mix_shell, :error, [" the dependency git_repo defined" <> _ = msg] } ->
399+
assert msg =~ "In custom/deps_repo/mix.exs:"
400+
assert msg =~ "{:git_repo, \"0.2.0\", [git: #{inspect fixture_path("git_repo")}]}"
401+
after
402+
0 -> flunk "expected diverged req error message"
403+
end
404+
end
405+
after
406+
Mix.Project.pop
407+
end
408+
377409
test "works with converged dependencies" do
378410
Mix.Project.push ConvergedDepsApp
379411

@@ -430,7 +462,7 @@ defmodule Mix.Tasks.DepsTest do
430462
Mix.Project.pop
431463
end
432464

433-
test "converged dependencies will error if not overriding" do
465+
test "converged dependencies errors if not overriding" do
434466
Mix.Project.push NonOverridenDepsApp
435467

436468
in_fixture "deps_status", fn ->
@@ -451,19 +483,7 @@ defmodule Mix.Tasks.DepsTest do
451483
Mix.Project.pop
452484
end
453485

454-
test "converged dependencies are properly ordered" do
455-
Mix.Project.push NestedDepsApp
456-
457-
in_fixture "deps_status", fn ->
458-
# Nested dependencies need to come first. They are
459-
# listed first, compiled first, etc.
460-
assert [Mix.Dep[app: :git_repo], Mix.Dep[app: :deps_repo]] = Mix.Deps.fetched
461-
end
462-
after
463-
Mix.Project.pop
464-
end
465-
466-
test "update parent dependencies" do
486+
test "updates parent dependencies" do
467487
Mix.Project.push NestedDepsApp
468488

469489
in_fixture "deps_status", fn ->
@@ -479,7 +499,7 @@ defmodule Mix.Tasks.DepsTest do
479499
Mix.Project.pop
480500
end
481501

482-
test "check if dependencies are using old elixir" do
502+
test "checks if dependencies are using old elixir version" do
483503
Mix.Project.push SuccessfulDepsApp
484504

485505
in_fixture "deps_status", fn ->
@@ -513,7 +533,7 @@ defmodule Mix.Tasks.DepsTest do
513533
end
514534
end
515535

516-
test "dont compile deps" do
536+
test "does not compile deps that have explicit flag" do
517537
Mix.Project.push NonCompilingDeps
518538

519539
in_fixture "deps_status", fn ->
@@ -539,7 +559,7 @@ defmodule Mix.Tasks.DepsTest do
539559
end
540560
end
541561

542-
test "converts duplicated deps at the same level" do
562+
test "converges duplicated deps at the same level" do
543563
Mix.Project.push DupDeps
544564

545565
in_fixture "deps_status", fn ->

0 commit comments

Comments
 (0)