Skip to content

Commit 8c57450

Browse files
author
José Valim
committed
Check the elixir req for dependencies too
Also streamline the workflow for deps.get and deps.update.
1 parent e999642 commit 8c57450

File tree

7 files changed

+124
-74
lines changed

7 files changed

+124
-74
lines changed

lib/mix/lib/mix/deps.ex

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ defmodule Mix.Deps do
145145
provided in the project are in the wrong format.
146146
"""
147147
def unfetched(acc, callback) do
148-
{ _deps, acc } = Mix.Deps.Converger.all(acc, callback)
149-
acc
148+
{ deps, acc } = Mix.Deps.Converger.all(acc, callback)
149+
{ Mix.Deps.Converger.topsort(deps), acc }
150150
end
151151

152152
@doc """
@@ -170,32 +170,6 @@ defmodule Mix.Deps do
170170
end)
171171
end
172172

173-
@doc """
174-
Receives a list of dependencies and returns the given list
175-
with their depending dependencies, recursively. It is expected
176-
the given list of dependencies to contain only fetched dependencies
177-
(since unfetched dependencies did not have their dependencies
178-
retrieved yet).
179-
"""
180-
def with_depending(deps, all_deps // fetched) do
181-
(deps ++ do_with_depending(deps, all_deps))
182-
|> Enum.uniq(&(&1.app))
183-
end
184-
185-
defp do_with_depending([], _all_deps) do
186-
[]
187-
end
188-
189-
defp do_with_depending(deps, all_deps) do
190-
dep_names = Enum.map(deps, fn dep -> dep.app end)
191-
192-
parents = Enum.filter all_deps, fn dep ->
193-
Enum.any?(dep.deps, fn child_dep -> child_dep.app in dep_names end)
194-
end
195-
196-
do_with_depending(parents, all_deps) ++ parents
197-
end
198-
199173
@doc """
200174
Runs the given `fun` inside the given dependency project by
201175
changing the current working directory and loading the given
@@ -240,7 +214,7 @@ defmodule Mix.Deps do
240214
def format_status(Mix.Dep[status: { :noappfile, path }]),
241215
do: "could not find an app file at #{Path.relative_to_cwd(path)}, " <>
242216
"this may happen when you specified the wrong application name in your deps " <>
243-
"or if the dependency failed to compile (which can be amended with `mix deps.compile`)"
217+
"or if the dependency did not compile (which can be amended with `mix deps.compile`)"
244218

245219
def format_status(Mix.Dep[status: { :invalidapp, path }]),
246220
do: "the app file at #{Path.relative_to_cwd(path)} is invalid"
@@ -278,6 +252,9 @@ defmodule Mix.Deps do
278252
def format_status(Mix.Dep[status: { :elixirlock, _ }]),
279253
do: "the dependency is built with an out-of-date elixir version, run `mix deps.get`"
280254

255+
def format_status(Mix.Dep[status: { :elixirreq, req }]),
256+
do: "the dependency requires Elixir #{req} but you are running on v#{System.version}"
257+
281258
defp dep_status(Mix.Dep[app: app, requirement: req, opts: opts, from: from]) do
282259
info = { app, req, Dict.drop(opts, [:dest, :lock, :env]) }
283260
"\n > In #{Path.relative_to_cwd(from)}:\n #{inspect info}\n"
@@ -308,26 +285,37 @@ defmodule Mix.Deps do
308285
Check if a dependency is ok.
309286
"""
310287
def ok?(Mix.Dep[status: { :ok, _ }]), do: true
311-
def ok?(_), do: false
288+
def ok?(Mix.Dep[]), do: false
312289

313290
@doc """
314-
Check if a dependency is available.
291+
Check if a dependency is available. Available dependencies
292+
are the ones that can be compiled, loaded, etc.
315293
"""
316294
def available?(Mix.Dep[status: { :overriden, _ }]), do: false
317295
def available?(Mix.Dep[status: { :diverged, _ }]), do: false
296+
def available?(Mix.Dep[status: { :elixirreq, _ }]), do: false
318297
def available?(Mix.Dep[status: { :unavailable, _ }]), do: false
319-
def available?(_), do: true
298+
def available?(Mix.Dep[]), do: true
320299

321300
@doc """
322-
Check if a dependency is out of date considering its
301+
Check if a dependency can be updated.
302+
"""
303+
def updatable?(Mix.Dep[status: { :elixirreq, _ }]), do: true
304+
def updatable?(dep), do: available?(dep)
305+
306+
@doc """
307+
Check if a dependency is out of date, also considering its
323308
lock status. Therefore, be sure to call `check_lock` before
324309
invoking this function.
310+
311+
Out of date dependencies are fixed by simply running `deps.get`.
325312
"""
326313
def out_of_date?(Mix.Dep[status: { :lockmismatch, _ }]), do: true
327314
def out_of_date?(Mix.Dep[status: :lockoutdated]), do: true
328315
def out_of_date?(Mix.Dep[status: :nolock]), do: true
329316
def out_of_date?(Mix.Dep[status: { :elixirlock, _ }]), do: true
330-
def out_of_date?(dep), do: not available?(dep)
317+
def out_of_date?(Mix.Dep[status: { :unavailable, _ }]), do: true
318+
def out_of_date?(Mix.Dep[]), do: false
331319
332320
@doc """
333321
Formats a dependency for printing.
@@ -406,6 +394,53 @@ defmodule Mix.Deps do
406394
407395
## Helpers
408396
397+
@doc false
398+
# Called by deps.get and deps.update
399+
def finalize(all_deps, apps, lock, opts) do
400+
deps = fetched_by_name(apps, all_deps)
401+
402+
# Do not attempt to compile dependencies that are not available.
403+
# mix deps.check at the end will emit proper status in case they failed.
404+
deps = Enum.filter(deps, &available?/1)
405+
406+
# Note we only retrieve the parent dependencies of the updated
407+
# deps if all dependencies are available. This is because if a
408+
# dependency is missing, it could be a children of the parent
409+
# (aka a sibling) which would make parent compilation fail.
410+
if Enum.all?(all_deps, &available?/1) do
411+
deps = with_depending(deps, all_deps)
412+
end
413+
414+
apps = Enum.map(deps, &(&1.app))
415+
Mix.Deps.Lock.write(lock)
416+
417+
unless opts[:no_compile] do
418+
args = if opts[:quiet], do: ["--quiet"|apps], else: apps
419+
Mix.Task.run("deps.compile", args)
420+
unless opts[:no_deps_check] do
421+
Mix.Task.run("deps.check", [])
422+
end
423+
end
424+
end
425+
426+
defp with_depending(deps, all_deps) do
427+
(deps ++ do_with_depending(deps, all_deps)) |> Enum.uniq(&(&1.app))
428+
end
429+
430+
defp do_with_depending([], _all_deps) do
431+
[]
432+
end
433+
434+
defp do_with_depending(deps, all_deps) do
435+
dep_names = Enum.map(deps, fn dep -> dep.app end)
436+
437+
parents = Enum.filter all_deps, fn dep ->
438+
Enum.any?(dep.deps, fn child_dep -> child_dep.app in dep_names end)
439+
end
440+
441+
do_with_depending(parents, all_deps) ++ parents
442+
end
443+
409444
defp to_app_names(given) do
410445
Enum.map given, fn(app) ->
411446
if is_binary(app), do: binary_to_atom(app), else: app

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,21 @@ defmodule Mix.Deps.Retriever do
128128

129129
defp mix_dep(Mix.Dep[opts: opts, app: app, status: status] = dep, config) do
130130
Mix.Deps.in_dependency(dep, config, fn _ ->
131+
config = Mix.project
131132
default =
132133
if Mix.Project.umbrella? do
133134
false
134135
else
135-
Path.join(Mix.project[:compile_path], "#{app}.app")
136+
Path.join(config[:compile_path], "#{app}.app")
136137
end
137138

138139
opts = Keyword.put_new(opts, :app, default)
139-
stat = if vsn = old_elixir_lock(), do: { :elixirlock, vsn }, else: status
140+
stat = cond do
141+
vsn = old_elixir_lock() -> { :elixirlock, vsn }
142+
req = old_elixir_req(config) -> { :elixirreq, req }
143+
true -> status
144+
end
145+
140146
dep.manager(:mix).opts(opts).deps(children).status(stat)
141147
end)
142148
end
@@ -191,7 +197,7 @@ defmodule Mix.Deps.Retriever do
191197
end
192198

193199
defp vsn_match?(nil, _actual), do: true
194-
defp vsn_match?(req, actual) when is_regex(req), do: actual =~ req
200+
defp vsn_match?(req, actual) when is_regex(req), do: actual =~ req
195201
defp vsn_match?(req, actual) when is_binary(req) do
196202
Version.match?(actual, req)
197203
end
@@ -202,4 +208,11 @@ defmodule Mix.Deps.Retriever do
202208
old_vsn
203209
end
204210
end
211+
212+
defp old_elixir_req(config) do
213+
req = config[:elixir]
214+
if req && not Version.match?(System.version, req) do
215+
req
216+
end
217+
end
205218
end

lib/mix/lib/mix/tasks/deps.check.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Mix.Tasks.Deps.Check do
22
use Mix.Task
33

4-
import Mix.Deps, only: [fetched: 0, format_dep: 1, format_status: 1, check_lock: 2, out_of_date?: 1]
4+
import Mix.Deps, only: [fetched: 0, format_dep: 1, format_status: 1, check_lock: 2]
55

66
@hidden true
77
@shortdoc "Check if all dependencies are valid"

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ defmodule Mix.Tasks.Deps.Compile do
2727
2828
"""
2929

30-
import Mix.Deps, only: [ fetched: 0, available?: 1, fetched_by_name: 2, compile_path: 1,
31-
with_depending: 2, format_dep: 1, make?: 1, mix?: 1,
32-
rebar?: 1 ]
30+
import Mix.Deps, only: [fetched: 0, available?: 1, fetched_by_name: 1, compile_path: 1,
31+
format_dep: 1, make?: 1, mix?: 1, rebar?: 1]
3332

3433
def run(args) do
3534
Mix.Project.get! # Require the project to be available
@@ -38,9 +37,7 @@ defmodule Mix.Tasks.Deps.Compile do
3837
{ opts, [], _ } ->
3938
do_run(Enum.filter(fetched, &available?/1), opts)
4039
{ opts, tail, _ } ->
41-
all_deps = fetched
42-
deps = fetched_by_name(tail, all_deps) |> with_depending(all_deps)
43-
do_run(deps, opts)
40+
do_run(fetched_by_name(tail), opts)
4441
end
4542
end
4643

@@ -111,7 +108,8 @@ defmodule Mix.Tasks.Deps.Compile do
111108
catch
112109
kind, reason ->
113110
Mix.shell.error "could not compile dependency #{app}, mix compile failed. " <>
114-
"If you want to recompile this dependency, please run: mix deps.compile #{app}"
111+
"You can recompile this dependency with `mix deps.compile #{app}` or " <>
112+
"update it with `mix deps.update #{app}`"
115113
:erlang.raise(kind, reason, System.stacktrace)
116114
after
117115
Mix.env(old_env)

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ defmodule Mix.Tasks.Deps.Get do
1515
1616
"""
1717

18-
import Mix.Deps, only: [unfetched: 2, unfetched_by_name: 3, format_dep: 1, check_lock: 2, out_of_date?: 1]
18+
import Mix.Deps, only: [unfetched: 2, unfetched_by_name: 3, format_dep: 1,
19+
check_lock: 2, out_of_date?: 1]
1920

2021
def run(args) do
2122
Mix.Project.get! # Require the project to be available
@@ -35,22 +36,11 @@ defmodule Mix.Tasks.Deps.Get do
3536
{ [], Mix.Deps.Lock.read }
3637
end
3738

38-
defp finalize_get({ apps, lock }, opts) do
39-
apps = Enum.reverse(apps)
39+
defp finalize_get({ all_deps, { apps, lock } }, opts) do
40+
Mix.Deps.finalize(all_deps, apps, lock, opts)
4041

41-
if apps == [] do
42-
unless opts[:no_deps_check], do: Mix.Task.run("deps.check", [])
43-
unless opts[:quiet], do: Mix.shell.info "All dependencies up to date"
44-
else
45-
Mix.Deps.Lock.write(lock)
46-
47-
unless opts[:no_compile] do
48-
args = if opts[:quiet], do: ["--quiet"|apps], else: apps
49-
Mix.Task.run("deps.compile", args)
50-
unless opts[:no_deps_check] do
51-
Mix.Task.run("deps.check", [])
52-
end
53-
end
42+
if apps == [] && !opts[:quiet] do
43+
Mix.shell.info "All dependencies up to date"
5444
end
5545
end
5646

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

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule Mix.Tasks.Deps.Update do
2020
2121
"""
2222

23-
import Mix.Deps, only: [ unfetched: 2, unfetched_by_name: 3, available?: 1, format_dep: 1 ]
23+
import Mix.Deps, only: [unfetched: 2, unfetched_by_name: 3, updatable?: 1, format_dep: 1]
2424

2525
def run(args) do
2626
Mix.Project.get! # Require the project to be available
@@ -43,21 +43,12 @@ defmodule Mix.Tasks.Deps.Update do
4343
{ [], Mix.Deps.Lock.read }
4444
end
4545

46-
defp finalize_update({ apps, lock }, opts) do
47-
apps = Enum.reverse(apps)
48-
Mix.Deps.Lock.write(lock)
49-
50-
unless opts[:no_compile] do
51-
args = if opts[:quiet], do: ["--quiet"|apps], else: apps
52-
Mix.Task.run("deps.compile", args)
53-
unless opts[:no_deps_check] do
54-
Mix.Task.run("deps.check", [])
55-
end
56-
end
46+
defp finalize_update({ all_deps, { apps, lock } }, opts) do
47+
Mix.Deps.finalize(all_deps, apps, lock, opts)
5748
end
5849

5950
defp deps_updater(dep, { acc, lock }) do
60-
if available?(dep) do
51+
if updatable?(dep) do
6152
Mix.Dep[app: app, scm: scm, opts: opts] = dep
6253
Mix.shell.info "* Updating #{format_dep(dep)}"
6354

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,29 @@ defmodule Mix.Tasks.DepsTest do
8888
Mix.Project.pop
8989
end
9090

91+
test "prints list of dependencies including elixir req mismatches" do
92+
Mix.Project.push ReqDepsApp
93+
94+
in_fixture "deps_status", fn ->
95+
File.write!("deps/ok/mix.exs", """)
96+
defmodule Deps.OkApp do
97+
use Mix.Project
98+
99+
def project do
100+
[elixir: "~> 0.1.0", app: :ok, version: "2.0"]
101+
end
102+
end
103+
"""
104+
105+
Mix.Tasks.Deps.run []
106+
assert_received { :mix_shell, :info, ["* ok (deps/ok)"] }
107+
message = " the dependency requires Elixir ~> 0.1.0 but you are running on v#{System.version}"
108+
assert_received { :mix_shell, :info, [^message] }
109+
end
110+
after
111+
Mix.Project.pop
112+
end
113+
91114
test "prints list of dependencies and their lock status" do
92115
Mix.Project.push DepsApp
93116

0 commit comments

Comments
 (0)