Skip to content

Commit 8da4936

Browse files
author
José Valim
committed
Ensure dependencies are properly skipped when running in another environment
Signed-off-by: José Valim <[email protected]>
1 parent bcc92cc commit 8da4936

File tree

4 files changed

+55
-45
lines changed

4 files changed

+55
-45
lines changed

lib/mix/lib/mix/dep/converger.ex

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,12 @@ defmodule Mix.Dep.Converger do
4949
end
5050

5151
defp all(acc, lock, opts, callback) do
52-
deps = Mix.Dep.Loader.children()
53-
deps = Enum.map(deps, &(%{&1 | top_level: true}))
54-
lock_given? = !!lock
52+
main = Mix.Dep.Loader.children()
53+
main = Enum.map(main, &(%{&1 | top_level: true}))
54+
apps = Enum.map(main, &(&1.app))
5555

56-
# Filter the dependencies per environment. We pass the ones
57-
# left out as accumulator and upper breadth to help catch
58-
# inconsistencies across environment, specially regarding
59-
# the :only option. They are filtered again later.
60-
current = Enum.map(deps, &(&1.app))
61-
{main, only} = Mix.Dep.Loader.partition_by_env(deps, opts)
56+
lock_given? = !!lock
57+
env = opts[:env]
6258

6359
# If no lock was given, let's read one to fill in the deps
6460
lock = lock || Mix.Dep.Lock.read
@@ -68,17 +64,14 @@ defmodule Mix.Dep.Converger do
6864
# lazily loaded, we need to check for it on every
6965
# iteration.
7066
{deps, rest, lock} =
71-
all(main, only, [], current, callback, acc, lock, fn dep ->
67+
all(main, apps, callback, acc, lock, env, fn dep ->
7268
if (remote = Mix.RemoteConverger.get) && remote.remote?(dep) do
7369
{:loaded, dep}
7470
else
7571
{:unloaded, dep, nil}
7672
end
7773
end)
7874

79-
# Filter deps per environment once more. If the filtered
80-
# dependencies had no conflicts, they are removed now.
81-
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, opts)
8275
diverged? = Enum.any?(deps, &Mix.Dep.diverged?/1)
8376

8477
# Run remote converger if one is available and rerun Mix's
@@ -99,19 +92,26 @@ defmodule Mix.Dep.Converger do
9992
# In case no lock was given, we will use the local lock
10093
# which is potentially stale. So remote.deps/2 needs to always
10194
# check if the data it finds in the lock is actually valid.
102-
all(main, [], [], Enum.map(main, &(&1.app)), callback, rest, lock, fn dep ->
103-
cond do
104-
cached = deps[dep.app] ->
105-
{:loaded, cached}
106-
true ->
107-
{:unloaded, dep, remote.deps(dep, lock)}
95+
all(main, apps, callback, rest, lock, env, fn dep ->
96+
if cached = deps[dep.app] do
97+
{:loaded, cached}
98+
else
99+
{:unloaded, dep, remote.deps(dep, lock)}
108100
end
109101
end)
110102
else
111103
{deps, rest, lock}
112104
end
113105
end
114106

107+
defp all(main, apps, callback, rest, lock, env, cache) do
108+
{deps, rest, lock} = all(main, [], [], apps, callback, rest, lock, env, cache)
109+
# When traversing dependencies, we keep skipped ones to
110+
# find conflicts. We remove them now after traversal.
111+
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, env)
112+
{deps, rest, lock}
113+
end
114+
115115
# We traverse the tree of dependencies in a breadth-first
116116
# fashion. The reason for this is that we converge
117117
# dependencies, but allow the parent to override any
@@ -151,10 +151,14 @@ defmodule Mix.Dep.Converger do
151151
# Now, since "d" was specified in a parent project, no
152152
# exception is going to be raised since d is considered
153153
# to be the authoritative source.
154-
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, cache) do
154+
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, env, cache) do
155155
cond do
156156
new_acc = diverged_deps(acc, upper_breadths, dep) ->
157-
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, cache)
157+
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, env, cache)
158+
Mix.Dep.Loader.skip?(dep, env) ->
159+
# We still keep skipped dependencies around to detect conflicts.
160+
# They must be rejected after every all iteration.
161+
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
158162
true ->
159163
dep =
160164
case cache.(dep) do
@@ -170,12 +174,15 @@ defmodule Mix.Dep.Converger do
170174
end
171175

172176
dep = %{dep | deps: reject_non_fullfilled_optional(dep.deps, current_breadths)}
173-
{acc, rest, lock} = all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, cache)
174-
all(dep.deps, acc, current_breadths, Enum.map(dep.deps, &(&1.app)) ++ current_breadths, callback, rest, lock, cache)
177+
{acc, rest, lock} =
178+
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
179+
180+
new_breadths = Enum.map(dep.deps, &(&1.app)) ++ current_breadths
181+
all(dep.deps, acc, current_breadths, new_breadths, callback, rest, lock, env, cache)
175182
end
176183
end
177184

178-
defp all([], acc, _upper, _current, _callback, rest, lock, _cache) do
185+
defp all([], acc, _upper, _current, _callback, rest, lock, _env, _cache) do
179186
{acc, rest, lock}
180187
end
181188

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ defmodule Mix.Dep.Loader do
2121
@doc """
2222
Partitions loaded dependencies by environment.
2323
"""
24-
def partition_by_env(deps, opts) do
25-
if env = opts[:env] do
26-
Enum.partition(deps, fn
27-
%Mix.Dep{status: {:divergedonly, _}} ->
28-
true
29-
%Mix.Dep{opts: opts} ->
30-
only = opts[:only] |> List.wrap |> validate_only!
31-
only == [] or env in List.wrap(only)
32-
end)
33-
else
34-
{deps, []}
35-
end
24+
def partition_by_env(deps, nil), do: {deps, []}
25+
def partition_by_env(deps, env), do: Enum.partition(deps, &not skip?(&1, env))
26+
27+
@doc """
28+
Check if a dependency must be skipped according to the environment.
29+
"""
30+
def skip?(_dep, nil), do: false
31+
def skip?(%Mix.Dep{status: {:divergedonly, _}}, _), do: false
32+
def skip?(%Mix.Dep{opts: opts}, env) do
33+
only = opts[:only]
34+
validate_only!(only)
35+
only != nil and not env in List.wrap(only)
3636
end
3737

3838
@doc """
@@ -283,7 +283,7 @@ defmodule Mix.Dep.Loader do
283283
end
284284

285285
defp validate_only!(only) do
286-
for entry <- only, not is_atom(entry) do
286+
for entry <- List.wrap(only), not is_atom(entry) do
287287
Mix.raise "Expected :only in dependency to be an atom or a list of atoms, got: #{inspect only}"
288288
end
289289
only
@@ -293,7 +293,7 @@ defmodule Mix.Dep.Loader do
293293
from = Path.absname("mix.exs")
294294
(Mix.Project.config[:deps] || [])
295295
|> Enum.map(&to_dep(&1, from))
296-
|> partition_by_env(opts)
296+
|> partition_by_env(opts[:env])
297297
|> elem(0)
298298
end
299299

lib/mix/test/mix/dep_test.exs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,8 @@ defmodule Mix.DepTest do
255255
deps = Mix.Dep.loaded([])
256256
assert length(deps) == 2
257257

258-
deps = Mix.Dep.loaded([env: :prod])
259-
assert length(deps) == 1
260-
assert Enum.find deps, &match?(%Mix.Dep{app: :foo}, &1)
258+
assert [dep] = Mix.Dep.loaded([env: :prod])
259+
assert dep.app == :foo
261260
end
262261
end
263262
end

lib/mix/test/mix/umbrella_test.exs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,19 @@ defmodule Mix.UmbrellaTest do
113113
end
114114
"""
115115

116-
# Should work across all environments
116+
# Does not fetch when filtered
117+
Mix.Tasks.Deps.Get.run ["--only", "dev"]
118+
refute_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
119+
120+
# But works across all environments
117121
Mix.Tasks.Deps.Get.run []
118122
assert_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
119123

120-
# Works on the current environment only
124+
# Does not show by default
121125
Mix.Tasks.Deps.run []
122-
refute_received {:mix_shell, :info, ["* git_repo " <> _]}
126+
refute_received {:mix_shell, :info, ["* git_repo" <> _]}
123127

124-
# Works on the other environment only
128+
# But shows on proper environment
125129
Mix.env(:other)
126130
Mix.Tasks.Deps.run []
127131
assert_received {:mix_shell, :info, ["* git_repo " <> _]}

0 commit comments

Comments
 (0)