Skip to content

Commit b326fe1

Browse files
author
José Valim
committed
Unify deps.get and deps.update workflows
1 parent 263f0d1 commit b326fe1

File tree

8 files changed

+133
-143
lines changed

8 files changed

+133
-143
lines changed

lib/mix/lib/mix/deps.ex

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -400,61 +400,6 @@ defmodule Mix.Deps do
400400

401401
## Helpers
402402

403-
@doc false
404-
# Called by deps.get and deps.update
405-
def finalize(all_deps, apps, lock, opts) do
406-
deps = loaded_by_name(apps, all_deps)
407-
408-
# Do not attempt to compile dependencies that are not available.
409-
# mix deps.check at the end will emit proper status in case they failed.
410-
deps = Enum.filter(deps, &available?/1)
411-
412-
# Note we only retrieve the parent dependencies of the updated
413-
# deps if all dependencies are available. This is because if a
414-
# dependency is missing, it could be a children of the parent
415-
# (aka a sibling) which would make parent compilation fail.
416-
#
417-
# If there is any other dependency that is not ok, we include
418-
# it for compilation too, this is our best to try to solve the
419-
# maximum we can at each deps.get and deps.update.
420-
if Enum.all?(all_deps, &available?/1) do
421-
deps = with_depending(deps, all_deps) ++
422-
Enum.filter(all_deps, fn dep -> not ok?(dep) end)
423-
end
424-
425-
apps = Enum.map(deps, &(&1.app)) |> Enum.uniq
426-
Mix.Deps.Lock.write(lock)
427-
428-
unless opts[:no_compile] do
429-
if apps != [] do
430-
args = if opts[:quiet], do: ["--quiet"|apps], else: apps
431-
Mix.Task.run("deps.compile", args)
432-
end
433-
434-
unless opts[:no_deps_check] do
435-
Mix.Task.run("deps.check", [])
436-
end
437-
end
438-
end
439-
440-
defp with_depending(deps, all_deps) do
441-
(deps ++ do_with_depending(deps, all_deps)) |> Enum.uniq(&(&1.app))
442-
end
443-
444-
defp do_with_depending([], _all_deps) do
445-
[]
446-
end
447-
448-
defp do_with_depending(deps, all_deps) do
449-
dep_names = Enum.map(deps, fn dep -> dep.app end)
450-
451-
parents = Enum.filter all_deps, fn dep ->
452-
Enum.any?(dep.deps, &(&1 in dep_names))
453-
end
454-
455-
do_with_depending(parents, all_deps) ++ parents
456-
end
457-
458403
defp to_app_names(given) do
459404
Enum.map given, fn(app) ->
460405
if is_binary(app), do: binary_to_atom(app), else: app

lib/mix/lib/mix/deps/fetcher.ex

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Module responsible for fetching (getting/updating)
2+
# dependencies from their sources.
3+
#
4+
# The new_lock and old_lock mechanism exists to signal
5+
# externally which dependencies need to be updated and
6+
# which ones do not.
7+
defmodule Mix.Deps.Fetcher do
8+
@moduledoc false
9+
10+
import Mix.Deps, only: [format_dep: 1, check_lock: 2, out_of_date?: 1, available?: 1, ok?: 1]
11+
12+
@doc """
13+
Fetches all dependencies.
14+
"""
15+
def all(old_lock, new_lock, opts) do
16+
do_finalize Mix.Deps.unloaded({ [], new_lock }, &do_fetch/2), old_lock, opts
17+
end
18+
19+
@doc """
20+
Fetches the dependencies with the given names and their children recursively.
21+
"""
22+
def by_name(names, old_lock, new_lock, opts) do
23+
do_finalize Mix.Deps.unloaded_by_name(names, { [], new_lock }, &do_fetch/2), old_lock, opts
24+
end
25+
26+
defp do_fetch(dep, { acc, lock }) do
27+
Mix.Dep[app: app, scm: scm, opts: opts] = dep = check_lock(dep, lock)
28+
29+
cond do
30+
# Dependencies that cannot be fetched are always compiled afterwards
31+
not scm.fetchable? ->
32+
{ dep, { [app|acc], lock } }
33+
34+
# If the dependency is not available or we have a lock mismatch
35+
out_of_date?(dep) ->
36+
new =
37+
if scm.checked_out?(opts) do
38+
Mix.shell.info "* Updating #{format_dep(dep)}"
39+
scm.update(opts)
40+
else
41+
Mix.shell.info "* Getting #{format_dep(dep)}"
42+
scm.checkout(opts)
43+
end
44+
45+
if new do
46+
{ dep, { [app|acc], Keyword.put(lock, app, new) } }
47+
else
48+
{ dep, { acc, lock } }
49+
end
50+
51+
# The dependency is ok or has some other error
52+
true ->
53+
{ dep, { acc, lock } }
54+
end
55+
end
56+
57+
defp do_finalize({ all_deps, { apps, new_lock } }, old_lock, opts) do
58+
# Let's get the loaded versions of deps
59+
deps = Mix.Deps.loaded_by_name(apps, all_deps)
60+
61+
# Do not attempt to compile dependencies that are not available.
62+
# mix deps.check at the end will emit proper status in case they failed.
63+
deps = Enum.filter(deps, &available?/1)
64+
65+
# Note we only retrieve the parent dependencies of the updated
66+
# deps if all dependencies are available. This is because if a
67+
# dependency is missing, it could be a children of the parent
68+
# (aka a sibling) which would make parent compilation fail.
69+
#
70+
# If there is any other dependency that is not ok, we include
71+
# it for compilation too, this is our best to try to solve the
72+
# maximum we can at each deps.get and deps.update.
73+
if Enum.all?(all_deps, &available?/1) do
74+
deps = with_depending(deps, all_deps) ++
75+
Enum.filter(all_deps, fn dep -> not ok?(dep) end)
76+
end
77+
78+
# Merge the new lock on top of the old to guarantee we don't
79+
# leave out things that could not be fetched and save it.
80+
lock = Dict.merge(old_lock, new_lock)
81+
Mix.Deps.Lock.write(lock)
82+
83+
do_compile(deps, opts)
84+
apps
85+
end
86+
87+
defp do_compile(deps, opts) do
88+
apps = Enum.map(deps, &(&1.app)) |> Enum.uniq
89+
90+
unless opts[:no_compile] do
91+
if apps != [] do
92+
args = if opts[:quiet], do: ["--quiet"|apps], else: apps
93+
Mix.Task.run("deps.compile", args)
94+
end
95+
96+
unless opts[:no_deps_check] do
97+
Mix.Task.run("deps.check", [])
98+
end
99+
end
100+
end
101+
102+
defp with_depending(deps, all_deps) do
103+
(deps ++ do_with_depending(deps, all_deps)) |> Enum.uniq(&(&1.app))
104+
end
105+
106+
defp do_with_depending([], _all_deps) do
107+
[]
108+
end
109+
110+
defp do_with_depending(deps, all_deps) do
111+
dep_names = Enum.map(deps, fn dep -> dep.app end)
112+
113+
parents = Enum.filter all_deps, fn dep ->
114+
Enum.any?(dep.deps, &(&1 in dep_names))
115+
end
116+
117+
do_with_depending(parents, all_deps) ++ parents
118+
end
119+
end

lib/mix/lib/mix/scm/path.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ defmodule Mix.SCM.Path do
2222
path = "../#{app}"
2323

2424
opts
25-
|> Keyword.put(:dest, Path.expand(path))
26-
|> Keyword.put_new(:path, path)
27-
|> Keyword.put_new(:env, Mix.env)
25+
|> Keyword.put(:dest, Path.expand(path))
26+
|> Keyword.put_new(:path, path)
27+
|> Keyword.put_new(:env, Mix.env)
2828
true ->
2929
nil
3030
end

lib/mix/lib/mix/tasks/deps.get.ex

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,64 +15,19 @@ defmodule Mix.Tasks.Deps.Get do
1515
1616
"""
1717

18-
import Mix.Deps, only: [unloaded: 2, unloaded_by_name: 3, format_dep: 1,
19-
check_lock: 2, out_of_date?: 1]
20-
2118
def run(args) do
2219
Mix.Project.get! # Require the project to be available
2320
{ opts, rest, _ } = OptionParser.parse(args, switches: [no_compile: :boolean, quiet: :boolean])
2421

25-
acc =
22+
apps =
2623
if rest != [] do
27-
unloaded_by_name(rest, init, &deps_getter/2)
24+
Mix.Deps.Fetcher.by_name(rest, [], Mix.Deps.Lock.read, opts)
2825
else
29-
unloaded(init, &deps_getter/2)
26+
Mix.Deps.Fetcher.all([], Mix.Deps.Lock.read, opts)
3027
end
3128

32-
finalize_get(acc, opts)
33-
end
34-
35-
defp init do
36-
{ [], Mix.Deps.Lock.read }
37-
end
38-
39-
defp finalize_get({ all_deps, { apps, lock } }, opts) do
40-
Mix.Deps.finalize(all_deps, apps, lock, opts)
41-
4229
if apps == [] && !opts[:quiet] do
4330
Mix.shell.info "All dependencies up to date"
4431
end
4532
end
46-
47-
defp deps_getter(dep, { acc, lock }) do
48-
shell = Mix.shell
49-
Mix.Dep[app: app, scm: scm, opts: opts] = dep = check_lock(dep, lock)
50-
51-
cond do
52-
# Dependencies that cannot be fetched are always compiled afterwards
53-
not scm.fetchable? ->
54-
{ dep, { [app|acc], lock } }
55-
56-
# If the dependency is not available or we have a lock mismatch
57-
out_of_date?(dep) ->
58-
shell.info "* Getting #{format_dep(dep)}"
59-
60-
new =
61-
if scm.checked_out?(opts) do
62-
scm.update(opts)
63-
else
64-
scm.checkout(opts)
65-
end
66-
67-
if new do
68-
{ dep, { [app|acc], Keyword.put(lock, app, new) } }
69-
else
70-
{ dep, { acc, lock } }
71-
end
72-
73-
# The dependency is ok or has some other error
74-
true ->
75-
{ dep, { acc, lock } }
76-
end
77-
end
7833
end

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

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,48 +20,25 @@ defmodule Mix.Tasks.Deps.Update do
2020
2121
"""
2222

23-
import Mix.Deps, only: [unloaded: 2, unloaded_by_name: 3, available?: 1, format_dep: 1]
24-
2523
def run(args) do
2624
Mix.Project.get! # Require the project to be available
2725
{ opts, rest, _ } = OptionParser.parse(args, switches: [no_compile: :boolean, all: :boolean])
2826

2927
cond do
3028
opts[:all] ->
31-
acc = unloaded(init, &deps_updater/2)
29+
Mix.Deps.Fetcher.all(Mix.Deps.Lock.read, [], opts)
3230
rest != [] ->
33-
acc = unloaded_by_name(rest, init, &deps_updater/2)
31+
{ old, new } = Dict.split(Mix.Deps.Lock.read, to_app_names(rest))
32+
Mix.Deps.Fetcher.by_name(rest, old, new, opts)
3433
true ->
3534
raise Mix.Error, message: "mix deps.update expects dependencies as arguments or " <>
3635
"the --all option to update all dependencies"
3736
end
38-
39-
finalize_update(acc, opts)
40-
end
41-
42-
defp init do
43-
{ [], Mix.Deps.Lock.read }
4437
end
4538

46-
defp finalize_update({ all_deps, { apps, lock } }, opts) do
47-
Mix.Deps.finalize(all_deps, apps, lock, opts)
48-
end
49-
50-
defp deps_updater(dep, { acc, lock }) do
51-
if available?(dep) do
52-
Mix.Dep[app: app, scm: scm, opts: opts] = dep
53-
Mix.shell.info "* Updating #{format_dep(dep)}"
54-
55-
lock =
56-
if latest = scm.update(opts) do
57-
Keyword.put(lock, app, latest)
58-
else
59-
lock
60-
end
61-
62-
{ dep, { [app|acc], lock } }
63-
else
64-
{ dep, { acc, lock } }
39+
defp to_app_names(given) do
40+
Enum.map given, fn(app) ->
41+
if is_binary(app), do: binary_to_atom(app), else: app
6542
end
6643
end
6744
end

lib/mix/test/mix/tasks/deps.git_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ defmodule Mix.Tasks.DepsGitTest do
241241
assert File.exists?("deps/git_repo/lib/git_repo.ex")
242242
assert File.read!("mix.lock") =~ last
243243

244-
message = "* Getting git_repo (#{fixture_path("git_repo")})"
244+
message = "* Updating git_repo (#{fixture_path("git_repo")})"
245245
assert_received { :mix_shell, :info, [^message] }
246246

247247
# Check we got no error

lib/mix/test/mix/tasks/deps.path_test.exs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ defmodule Mix.Tasks.DepsPathTest do
1515
end
1616
end
1717

18-
test "updates and cleans path repos with compilation" do
18+
test "compiles path repos on update" do
1919
Mix.Project.push DepsApp
2020

2121
in_fixture "deps_status", fn ->
2222
Mix.Tasks.Deps.Update.run ["--all"]
23-
assert_received { :mix_shell, :info, ["* Updating raw_repo (custom/raw_repo)"] }
2423
assert_received { :mix_shell, :info, ["Compiled lib/raw_repo.ex"] }
2524
assert_received { :mix_shell, :info, ["Generated raw_repo.app"] }
2625
assert File.exists?("_build/shared/lib/raw_repo/ebin/Elixir.RawRepo.beam")

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ defmodule Mix.Tasks.DepsTest do
378378
Mix.Task.clear
379379

380380
Mix.Tasks.Deps.Update.run ["--all"]
381-
assert_received { :mix_shell, :info, ["* Updating deps_repo (custom/deps_repo)"] }
382381
assert_received { :mix_shell, :info, ["* Compiling deps_repo"] }
383382
end
384383
end
@@ -491,8 +490,6 @@ defmodule Mix.Tasks.DepsTest do
491490
Mix.Task.clear
492491
Mix.Tasks.Deps.Update.run ["--all"]
493492

494-
message = "* Updating deps_repo (custom/deps_repo)"
495-
assert_received { :mix_shell, :info, [^message] }
496493
message = "* Updating git_repo (#{fixture_path("git_repo")})"
497494
assert_received { :mix_shell, :info, [^message] }
498495

@@ -518,8 +515,6 @@ defmodule Mix.Tasks.DepsTest do
518515
Mix.Task.clear
519516
Mix.Tasks.Deps.Update.run ["--all"]
520517

521-
message = "* Updating bad_deps_repo (custom/bad_deps_repo)"
522-
assert_received { :mix_shell, :info, [^message] }
523518
message = "* Updating git_repo (#{fixture_path("git_repo")})"
524519
assert_received { :mix_shell, :info, [^message] }
525520
end

0 commit comments

Comments
 (0)