Skip to content

Commit 1d87a77

Browse files
committed
Handle rebar dependencies better
Download dependencies specified in rebar.config and go through Converger to handle conflicts. Handle `sub_dirs` in rebar.config when getting dependencies and by the sub_dirs to the code load path.
1 parent df51fbe commit 1d87a77

File tree

5 files changed

+175
-42
lines changed

5 files changed

+175
-42
lines changed

lib/mix/lib/mix/deps.ex

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defrecord Mix.Dep, [ scm: nil, app: nil, requirement: nil, status: nil, opts: nil,
2-
project: nil, deps: [] ] do
2+
project: nil, deps: [], rebar: nil ] do
33
@moduledoc """
44
This is a record that keeps information about your project
55
dependencies. It keeps:
@@ -81,7 +81,7 @@ defmodule Mix.Deps do
8181
index = Mix.Dep.__index__(:app)
8282
Enum.each apps, fn(app) ->
8383
unless List.keyfind(deps, app, index) do
84-
Mix.shell.info message: "unknown dependency #{app} for env #{Mix.env}"
84+
Mix.shell.info "unknown dependency #{app} for env #{Mix.env}"
8585
end
8686
end
8787

@@ -221,13 +221,21 @@ defmodule Mix.Deps do
221221
Returns all load paths for the dependency.
222222
"""
223223
def load_paths(Mix.Dep[app: app, opts: opts] = dep) do
224-
if mix?(dep) do
225-
paths = Mix.Project.in_project app, opts[:dest], fn _ ->
226-
Mix.Project.load_paths
227-
end
228-
Enum.uniq paths
229-
else
230-
[ Path.join(opts[:dest], "ebin") ]
224+
cond do
225+
mix?(dep) ->
226+
paths = Mix.Project.in_project app, opts[:dest], fn _ ->
227+
Mix.Project.load_paths
228+
end
229+
Enum.uniq paths
230+
rebar?(dep) ->
231+
# Add root dir and all sub dirs with ebin/ directory
232+
[ opts[:dest] | dep.rebar[:sub_dirs] ]
233+
|> Enum.map(Path.wildcard(&1))
234+
|> List.concat
235+
|> Enum.map(fn path -> Path.join([opts[:dest], path, "ebin"]) end)
236+
|> Enum.filter(File.dir?(&1))
237+
true ->
238+
[ Path.join(opts[:dest], "ebin") ]
231239
end
232240
end
233241

@@ -242,9 +250,7 @@ defmodule Mix.Deps do
242250
Returns true if dependency is a rebar project.
243251
"""
244252
def rebar?(dep) do
245-
Enum.any? ["rebar.config", "rebar.config.script"], fn file ->
246-
File.regular? Path.join(dep.opts[:dest], file)
247-
end
253+
dep.rebar != nil
248254
end
249255

250256
@doc """

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule Mix.Deps.Converger do
2020
def all(rest, callback) do
2121
config = [ deps_path: Path.expand(Mix.project[:deps_path]),
2222
root_lockfile: Path.expand(Mix.project[:lockfile]) ]
23-
{ main, rest } = Mix.Deps.Retriever.all(rest, config,callback)
23+
{ main, rest } = Mix.Deps.Retriever.all(rest, config, callback)
2424
{ all(Enum.reverse(main), [], [], main), rest }
2525
end
2626

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

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ defmodule Mix.Deps.Retriever do
1414
provides a dependency in the wrong format.
1515
"""
1616
def all(post_config // []) do
17-
{ deps, _ } = all(nil, post_config, fn(dep, acc) -> { dep, acc } end)
17+
{ deps, _ } = all(nil, children, post_config, fn(dep, acc) -> { dep, acc } end)
1818
deps
1919
end
2020

@@ -24,22 +24,34 @@ defmodule Mix.Deps.Retriever do
2424
in case some processing is done.
2525
"""
2626
def all(rest, post_config // [], callback) do
27-
Enum.map_reduce children, rest, fn (dep, rest) ->
28-
{ dep, rest } = callback.(dep, rest)
27+
all(rest, children, post_config, callback)
28+
end
2929

30-
if Mix.Deps.available?(dep) and mixfile?(dep) do
31-
{ dep, rest } = Mix.Deps.in_dependency dep, post_config, fn project ->
32-
{ deps, rest } = all(rest, callback)
30+
defp all(rest, childs, post_config, callback) do
31+
Enum.map_reduce childs, rest, fn (dep, rest) ->
32+
{ dep, rest } = callback.(dep, rest)
3333

34-
# We need to call with_mix_project once again
35-
# here in case the dependency was not available
36-
# the first time and the callback hook just
37-
# happened to fetch it.
38-
{ with_mix_project(dep, project).deps(deps), rest }
39-
end
34+
cond do
35+
Mix.Deps.available?(dep) and mixfile?(dep) ->
36+
Mix.Deps.in_dependency(dep, post_config, fn project ->
37+
{ deps, rest } = all(rest, children, [], callback)
38+
39+
# We need to call with_mix_project once again
40+
# here in case the dependency was not available
41+
# the first time and the callback hook just
42+
# happened to fetch it.
43+
{ with_mix_project(dep, project).deps(deps), rest }
44+
end)
45+
46+
Mix.Deps.available?(dep) and rebarconfig?(dep) ->
47+
dir = dep.opts[:dest]
48+
config = Mix.Rebar.load_config(dir)
49+
{ deps, rest } = all(rest, rebar_children(dir), [], callback)
50+
{ dep.rebar(config).deps(deps), rest }
51+
52+
true ->
53+
{ dep, rest }
4054
end
41-
42-
{ dep, rest }
4355
end
4456
end
4557

@@ -49,20 +61,9 @@ defmodule Mix.Deps.Retriever do
4961
field is not populated.
5062
"""
5163
def children() do
52-
deps = Mix.project[:deps] || []
53-
scms = Mix.SCM.available
54-
55-
Enum.map deps, fn dep ->
56-
dep = with_scm_and_status(dep, scms)
57-
58-
if Mix.Deps.available?(dep) and mixfile?(dep) do
59-
Mix.Deps.in_dependency dep, fn project ->
60-
with_mix_project(dep, project)
61-
end
62-
else
63-
dep
64-
end
65-
end
64+
Mix.Project.recur(fn _ ->
65+
(Mix.project[:deps] || []) |> setup_deps
66+
end) |> List.concat
6667
end
6768

6869
@doc """
@@ -74,6 +75,34 @@ defmodule Mix.Deps.Retriever do
7475

7576
## Helpers
7677

78+
defp rebar_children(dir) do
79+
Mix.Rebar.recur(dir, fn config ->
80+
Mix.Rebar.deps(config) |> setup_deps
81+
end) |> List.concat
82+
end
83+
84+
defp setup_deps(deps) do
85+
scms = Mix.SCM.available
86+
87+
Enum.map deps, fn dep ->
88+
dep = with_scm_and_status(dep, scms)
89+
90+
cond do
91+
Mix.Deps.available?(dep) and mixfile?(dep) ->
92+
Mix.Deps.in_dependency(dep, fn project ->
93+
with_mix_project(dep, project)
94+
end)
95+
Mix.Deps.available?(dep) and rebarconfig?(dep) ->
96+
config = Mix.Rebar.load_config(dep.opts[:dest])
97+
with_rebar_config(dep, config)
98+
true ->
99+
dep
100+
end
101+
end
102+
end
103+
104+
defp with_rebar_config(dep, _config), do: dep
105+
77106
defp with_mix_project(Mix.Dep[project: nil] = dep, project) do
78107
if match?({ :noappfile, _ }, dep.status) and Mix.Project.umbrella? do
79108
dep = dep.update_opts(Keyword.put(&1, :app, false))
@@ -156,6 +185,12 @@ defmodule Mix.Deps.Retriever do
156185
defp vsn_match?(expected, actual) when is_regex(expected), do: actual =~ expected
157186

158187
defp mixfile?(dep) do
159-
File.regular?(Path.join dep.opts[:dest], "mix.exs")
188+
File.regular?(Path.join(dep.opts[:dest], "mix.exs"))
189+
end
190+
191+
defp rebarconfig?(dep) do
192+
Enum.any?(["rebar.config", "rebar.config.script"], fn file ->
193+
File.regular?(Path.join(dep.opts[:dest], file))
194+
end)
160195
end
161196
end

lib/mix/lib/mix/rebar.ex

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
defmodule Mix.Rebar do
2+
@moduledoc false
3+
4+
@doc """
5+
Loads the rebar.config and evaluates rebar.config.script if it exists in the
6+
given directory.
7+
"""
8+
def load_config(dir) do
9+
config_path = Path.join(dir, "rebar.config")
10+
script_path = Path.join(dir, "rebar.config.script")
11+
12+
config = case :file.consult(config_path) do
13+
{ :ok, config } ->
14+
config
15+
{ :error, :enoent } ->
16+
[]
17+
{ :error, error } ->
18+
reason = :file.format_error(error)
19+
raise Mix.Error, message: "Error consulting rebar config #{config_path}: #{reason}"
20+
end
21+
22+
if File.exists?(script_path) do
23+
eval_script(script_path, config)
24+
else
25+
config
26+
end
27+
end
28+
29+
@doc """
30+
Parses the dependencies in given rebar.config to Mix's dependency format.
31+
"""
32+
def deps(config) do
33+
if deps = config[:deps] do
34+
Enum.map(deps, fn({ app, req, source }) ->
35+
{ scm, url } = case source do
36+
{ scm, url } -> { scm, to_binary(url) }
37+
{ scm, url, _ } -> { scm, to_binary(url) }
38+
end
39+
40+
opts = case source do
41+
{ _, _, "" } -> [branch: "HEAD"]
42+
{ _, _, { :branch, branch } } -> [branch: to_binary(branch)]
43+
{ _, _, { :tag, tag } } -> [tag: to_binary(tag)]
44+
{ _, _, ref } -> [ref: to_binary(ref)]
45+
_ -> []
46+
end
47+
{ app, to_binary(req), [{scm, to_binary(url)}|opts] }
48+
end)
49+
else
50+
[]
51+
end
52+
end
53+
54+
@doc """
55+
Runs `fun` inside the given directory and all specified `sub_dirs` in the
56+
rebar config in the directory.
57+
"""
58+
def recur(dir, fun) do
59+
config = load_config(dir)
60+
61+
if sub_dirs = config[:sub_dirs] do
62+
sub_dirs = sub_dirs
63+
|> Enum.map(Path.wildcard(&1))
64+
|> List.concat
65+
|> Enum.filter(File.dir?(&1))
66+
67+
Enum.map(sub_dirs, fn(dir) ->
68+
recur(dir, fun)
69+
end) |> List.concat
70+
end
71+
72+
[File.cd!(dir, fn -> fun.(config) end)]
73+
end
74+
75+
defp eval_script(path, config) do
76+
script = Path.basename(path)
77+
case :file.script(path, eval_binds(CONFIG: config, SCRIPT: script)) do
78+
{ :ok, config } ->
79+
config
80+
{ :error, error } ->
81+
reason = :file.format_error(error)
82+
raise Mix.Error, message: "Error evaluating rebar config script #{path}: #{reason}"
83+
end
84+
end
85+
86+
defp eval_binds(binds) do
87+
Enum.reduce(binds, :erl_eval.new_bindings, fn ({k, v}, binds) ->
88+
:erl_eval.add_binding(k, v, binds)
89+
end)
90+
end
91+
end

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ defmodule Mix.Tasks.Deps.Compile do
121121
end
122122

123123
defp do_compile(app, command) when is_binary(command) do
124+
Mix.shell.info("#{app}: #{command}")
124125
if Mix.shell.cmd(command) != 0 do
125126
raise Mix.Error, message: "could not compile dependency #{app}, custom #{command} command failed." <>
126127
"In case you want to recompile this dependency, please run: mix deps.compile #{app}"

0 commit comments

Comments
 (0)