Skip to content

Commit 1250c70

Browse files
author
José Valim
committed
Merge pull request #1068 from ericmj/mix-propagate-deps
Propagate dependencies in mix tasks
2 parents 6e018c5 + 62b6ed1 commit 1250c70

File tree

6 files changed

+102
-25
lines changed

6 files changed

+102
-25
lines changed

lib/mix/lib/mix/deps.ex

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
defrecord Mix.Dep, [scm: nil, app: nil, requirement: nil, status: nil, opts: nil, project: nil] do
1+
defrecord Mix.Dep, [ scm: nil, app: nil, requirement: nil, status: nil, opts: nil,
2+
project: nil, deps: [] ] do
23
@moduledoc """
34
This is a record that keeps information about your project
45
dependencies. It keeps:
@@ -46,18 +47,29 @@ defmodule Mix.Deps do
4647
Mix.Deps.Project.all
4748
end
4849

50+
@doc """
51+
Returns all dependencies depending on given dependencies.
52+
"""
53+
def depending(deps, all_deps // all) do
54+
dep_names = Enum.map(deps, fn dep -> dep.app end)
55+
parents = Enum.filter all_deps, fn dep ->
56+
Enum.any?(dep.deps, fn child_dep -> child_dep.app in dep_names end)
57+
end
58+
parents ++ (if parents != [], do: depending(parents, all_deps), else: [])
59+
end
60+
4961
@doc """
5062
Receives a list of deps names and returns deps records.
5163
Raises an error if the dependency does not exist.
5264
"""
53-
def by_name!(given) do
65+
def by_name!(given, all_deps // all) do
5466
# Ensure all apps are atoms
5567
apps = Enum.map given, fn(app) ->
5668
if is_binary(app), do: binary_to_atom(app), else: app
5769
end
5870

5971
# We need to keep the order of all, which properly orders deps
60-
deps = Enum.filter all, fn(dep) -> dep.app in apps end
72+
deps = Enum.filter all_deps, fn(dep) -> dep.app in apps end
6173

6274
# Now we validate the given atoms
6375
index = Mix.Dep.__index__(:app)
@@ -70,6 +82,23 @@ defmodule Mix.Deps do
7082
deps
7183
end
7284

85+
@doc """
86+
Runs the given `fun` inside the given dependency project by
87+
changing the current working directory and loading the given
88+
project into the project stack.
89+
"""
90+
def in_dependency(Mix.Dep[app: app, opts: opts], post_config // [], fun) do
91+
env = opts[:env] || :prod
92+
old_env = Mix.env
93+
94+
try do
95+
Mix.env(env)
96+
Mix.Project.in_project(app, opts[:dest], post_config, fun)
97+
after
98+
Mix.env(old_env)
99+
end
100+
end
101+
73102
@doc """
74103
Formats the status of a dependency.
75104
"""

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

+29-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ defmodule Mix.Deps.Converger do
2323
main = Enum.reverse Mix.Deps.Project.all
2424
config = [ deps_path: Path.expand(Mix.project[:deps_path]),
2525
root_lockfile: Path.expand(Mix.project[:lockfile]) ]
26-
all(main, [], [], main, config, callback, rest)
26+
{ deps, rest } = all(main, [], [], main, config, callback, rest)
27+
{ nest_deps(deps, config), rest }
2728
end
2829

2930
# We traverse the tree of dependencies in a breadth-
@@ -115,19 +116,35 @@ defmodule Mix.Deps.Converger do
115116
File.regular?(Path.join dep.opts[:dest], "mix.exs")
116117
end
117118

119+
# Sets the `deps` field to the child dependencies of all
120+
# given dependencies and does so recursively.
121+
defp nest_deps(deps, config) do
122+
Enum.map deps, fn dep ->
123+
nest_deps(dep, deps, config)
124+
end
125+
end
126+
127+
defp nest_deps(dep, deps, config) do
128+
if available?(dep) and mixfile?(dep) do
129+
nested_apps = nested_names(dep, config)
130+
sub_deps = Enum.filter(deps, fn dep -> dep.app in nested_apps end)
131+
dep.deps Enum.map(sub_deps, nest_deps(&1, deps, config))
132+
else
133+
dep
134+
end
135+
end
136+
137+
defp nested_names(dep, post_config) do
138+
Mix.Deps.in_dependency dep, post_config, fn _ ->
139+
Mix.Deps.Project.all_names
140+
end
141+
end
142+
118143
# The dependency contains a Mixfile, so let's
119144
# load it and retrieve its nested dependencies.
120-
defp nested_deps(Mix.Dep[app: app, opts: opts], post_config) do
121-
env = opts[:env] || :prod
122-
old_env = Mix.env
123-
124-
try do
125-
Mix.env(env)
126-
Mix.Project.in_project(app, opts[:dest], post_config, fn project ->
127-
{ project, Enum.reverse Mix.Deps.Project.all }
128-
end)
129-
after
130-
Mix.env(old_env)
145+
defp nested_deps(dep, post_config) do
146+
Mix.Deps.in_dependency dep, post_config, fn project ->
147+
{ project, Enum.reverse Mix.Deps.Project.all }
131148
end
132149
end
133150
end

lib/mix/lib/mix/deps/project.ex

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ defmodule Mix.Deps.Project do
1919
Enum.map deps, with_scm_and_status(&1, scms)
2020
end
2121

22+
@doc """
23+
Returns all application names of current project's dependencies.
24+
"""
25+
def all_names do
26+
deps = Mix.project[:deps] || []
27+
Enum.map(deps, elem(&1, 0))
28+
end
29+
2230
@doc """
2331
Receives a dependency and update its status.
2432
"""
@@ -98,4 +106,4 @@ defmodule Mix.Deps.Project do
98106
defp vsn_match?(nil, _actual), do: true
99107
defp vsn_match?(expected, actual) when is_binary(expected), do: actual == expected
100108
defp vsn_match?(expected, actual) when is_regex(expected), do: actual =~ expected
101-
end
109+
end

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ defmodule Mix.Tasks.Deps.Compile do
2424
2525
"""
2626

27-
import Mix.Deps, only: [ all: 0, available?: 1, by_name!: 1, compile_paths: 1,
28-
format_dep: 1, make?: 1, mix?: 1, rebar?: 1 ]
27+
import Mix.Deps, only: [ all: 0, available?: 1, by_name!: 2, compile_paths: 1,
28+
depending: 2, format_dep: 1, make?: 1, mix?: 1, rebar?: 1 ]
2929

3030
def run(args) do
3131
case OptionParser.parse(args) do
3232
{ _, [] } ->
3333
do_compile Enum.filter(all, available?(&1))
3434
{ _, tail } ->
35-
do_compile by_name!(tail)
35+
all_deps = all
36+
deps = by_name!(tail, all_deps)
37+
do_compile(deps ++ depending(deps, all_deps))
3638
end
3739
end
3840

lib/mix/lib/mix/tasks/deps.update.ex

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ defmodule Mix.Tasks.Deps.Update do
1616
* `--no-compile` skip compilation of dependencies
1717
"""
1818

19-
import Mix.Deps, only: [all: 2, available?: 1, by_name!: 1, format_dep: 1]
19+
import Mix.Deps, only: [ all: 0, all: 2, available?: 1, by_name!: 2,
20+
depending: 2, format_dep: 1 ]
2021

2122
def run(args) do
2223
{ opts, rest } = OptionParser.parse(args, switches: [no_compile: :boolean])
2324

2425
if rest != [] do
25-
deps = Enum.map by_name!(rest), check_unavailable!(&1)
26+
all_deps = all
27+
deps = Enum.map by_name!(rest, all_deps), check_unavailable!(&1)
28+
deps = deps ++ depending(deps, all_deps)
2629
{ _, acc } = Enum.map_reduce deps, init, deps_updater(&1, &2)
2730
else
2831
acc = all(init, deps_updater(&1, &2))

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

+22-4
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ defmodule Mix.Tasks.DepsTest do
257257
assert_received { :mix_shell, :info, ["Generated git_repo.app"] }
258258

259259
Mix.Tasks.Deps.Update.run []
260-
assert_received { :mix_shell, :info, ["* Updating deps_repo [path: \"custom/deps_repo\"]"] }
260+
assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] }
261261
end
262262
after
263263
purge [GitRepo, GitRepo.Mix, DepsRepo]
@@ -290,8 +290,9 @@ defmodule Mix.Tasks.DepsTest do
290290

291291
Mix.Task.clear
292292
Mix.Tasks.Deps.Update.run []
293-
assert_received { :mix_shell, :info, ["* Updating deps_repo [path: \"custom/deps_repo\"]"] }
294-
assert_received { :mix_shell, :info, ["* Updating bad_deps_repo [path: \"custom/bad_deps_repo\"]"] }
293+
294+
assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] }
295+
assert_received { :mix_shell, :info, ["* Updating bad_deps_repo (0.1.0) [path: \"custom/bad_deps_repo\"]"] }
295296

296297
Mix.Tasks.Deps.Check.run []
297298
end
@@ -312,4 +313,21 @@ defmodule Mix.Tasks.DepsTest do
312313
purge [GitRepo, GitRepo.Mix, DepsRepo, BadDepsRepo]
313314
Mix.Project.pop
314315
end
315-
end
316+
317+
test "update parent dependencies" do
318+
Mix.Project.push NestedDepsApp
319+
320+
in_fixture "deps_status", fn ->
321+
Mix.Tasks.Deps.Get.run []
322+
Mix.Task.clear
323+
Mix.Tasks.Deps.Update.run ["git_repo"]
324+
325+
message = "* Updating git_repo (0.1.0) [git: \"#{fixture_path("git_repo")}\"]"
326+
assert_received { :mix_shell, :info, [^message] }
327+
assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] }
328+
end
329+
after
330+
purge [GitRepo, GitRepo.Mix, DepsRepo]
331+
Mix.Project.pop
332+
end
333+
end

0 commit comments

Comments
 (0)