diff --git a/lib/mix/lib/mix/deps.ex b/lib/mix/lib/mix/deps.ex index b868a6640d2..be9569a75e8 100644 --- a/lib/mix/lib/mix/deps.ex +++ b/lib/mix/lib/mix/deps.ex @@ -1,4 +1,5 @@ -defrecord Mix.Dep, [scm: nil, app: nil, requirement: nil, status: nil, opts: nil, project: nil] do +defrecord Mix.Dep, [ scm: nil, app: nil, requirement: nil, status: nil, opts: nil, + project: nil, deps: [] ] do @moduledoc """ This is a record that keeps information about your project dependencies. It keeps: @@ -46,18 +47,29 @@ defmodule Mix.Deps do Mix.Deps.Project.all end + @doc """ + Returns all dependencies depending on given dependencies. + """ + def depending(deps, all_deps // all) do + dep_names = Enum.map(deps, fn dep -> dep.app end) + parents = Enum.filter all_deps, fn dep -> + Enum.any?(dep.deps, fn child_dep -> child_dep.app in dep_names end) + end + parents ++ (if parents != [], do: depending(parents, all_deps), else: []) + end + @doc """ Receives a list of deps names and returns deps records. Raises an error if the dependency does not exist. """ - def by_name!(given) do + def by_name!(given, all_deps // all) do # Ensure all apps are atoms apps = Enum.map given, fn(app) -> if is_binary(app), do: binary_to_atom(app), else: app end # We need to keep the order of all, which properly orders deps - deps = Enum.filter all, fn(dep) -> dep.app in apps end + deps = Enum.filter all_deps, fn(dep) -> dep.app in apps end # Now we validate the given atoms index = Mix.Dep.__index__(:app) @@ -70,6 +82,23 @@ defmodule Mix.Deps do deps end + @doc """ + Runs the given `fun` inside the given dependency project by + changing the current working directory and loading the given + project into the project stack. + """ + def in_dependency(Mix.Dep[app: app, opts: opts], post_config // [], fun) do + env = opts[:env] || :prod + old_env = Mix.env + + try do + Mix.env(env) + Mix.Project.in_project(app, opts[:dest], post_config, fun) + after + Mix.env(old_env) + end + end + @doc """ Formats the status of a dependency. """ diff --git a/lib/mix/lib/mix/deps/converger.ex b/lib/mix/lib/mix/deps/converger.ex index e10cbb5f14c..1fc054ef788 100644 --- a/lib/mix/lib/mix/deps/converger.ex +++ b/lib/mix/lib/mix/deps/converger.ex @@ -23,7 +23,8 @@ defmodule Mix.Deps.Converger do main = Enum.reverse Mix.Deps.Project.all config = [ deps_path: Path.expand(Mix.project[:deps_path]), root_lockfile: Path.expand(Mix.project[:lockfile]) ] - all(main, [], [], main, config, callback, rest) + { deps, rest } = all(main, [], [], main, config, callback, rest) + { nest_deps(deps, config), rest } end # We traverse the tree of dependencies in a breadth- @@ -115,19 +116,35 @@ defmodule Mix.Deps.Converger do File.regular?(Path.join dep.opts[:dest], "mix.exs") end + # Sets the `deps` field to the child dependencies of all + # given dependencies and does so recursively. + defp nest_deps(deps, config) do + Enum.map deps, fn dep -> + nest_deps(dep, deps, config) + end + end + + defp nest_deps(dep, deps, config) do + if available?(dep) and mixfile?(dep) do + nested_apps = nested_names(dep, config) + sub_deps = Enum.filter(deps, fn dep -> dep.app in nested_apps end) + dep.deps Enum.map(sub_deps, nest_deps(&1, deps, config)) + else + dep + end + end + + defp nested_names(dep, post_config) do + Mix.Deps.in_dependency dep, post_config, fn _ -> + Mix.Deps.Project.all_names + end + end + # The dependency contains a Mixfile, so let's # load it and retrieve its nested dependencies. - defp nested_deps(Mix.Dep[app: app, opts: opts], post_config) do - env = opts[:env] || :prod - old_env = Mix.env - - try do - Mix.env(env) - Mix.Project.in_project(app, opts[:dest], post_config, fn project -> - { project, Enum.reverse Mix.Deps.Project.all } - end) - after - Mix.env(old_env) + defp nested_deps(dep, post_config) do + Mix.Deps.in_dependency dep, post_config, fn project -> + { project, Enum.reverse Mix.Deps.Project.all } end end end diff --git a/lib/mix/lib/mix/deps/project.ex b/lib/mix/lib/mix/deps/project.ex index c9b86c4e845..6473e88b5f1 100644 --- a/lib/mix/lib/mix/deps/project.ex +++ b/lib/mix/lib/mix/deps/project.ex @@ -19,6 +19,14 @@ defmodule Mix.Deps.Project do Enum.map deps, with_scm_and_status(&1, scms) end + @doc """ + Returns all application names of current project's dependencies. + """ + def all_names do + deps = Mix.project[:deps] || [] + Enum.map(deps, elem(&1, 0)) + end + @doc """ Receives a dependency and update its status. """ @@ -98,4 +106,4 @@ defmodule Mix.Deps.Project do defp vsn_match?(nil, _actual), do: true defp vsn_match?(expected, actual) when is_binary(expected), do: actual == expected defp vsn_match?(expected, actual) when is_regex(expected), do: actual =~ expected -end \ No newline at end of file +end diff --git a/lib/mix/lib/mix/tasks/deps.compile.ex b/lib/mix/lib/mix/tasks/deps.compile.ex index 9f604b9da69..cb556365ce5 100644 --- a/lib/mix/lib/mix/tasks/deps.compile.ex +++ b/lib/mix/lib/mix/tasks/deps.compile.ex @@ -24,15 +24,17 @@ defmodule Mix.Tasks.Deps.Compile do """ - import Mix.Deps, only: [ all: 0, available?: 1, by_name!: 1, compile_paths: 1, - format_dep: 1, make?: 1, mix?: 1, rebar?: 1 ] + import Mix.Deps, only: [ all: 0, available?: 1, by_name!: 2, compile_paths: 1, + depending: 2, format_dep: 1, make?: 1, mix?: 1, rebar?: 1 ] def run(args) do case OptionParser.parse(args) do { _, [] } -> do_compile Enum.filter(all, available?(&1)) { _, tail } -> - do_compile by_name!(tail) + all_deps = all + deps = by_name!(tail, all_deps) + do_compile(deps ++ depending(deps, all_deps)) end end diff --git a/lib/mix/lib/mix/tasks/deps.update.ex b/lib/mix/lib/mix/tasks/deps.update.ex index 065040affd5..6a9d1a30589 100644 --- a/lib/mix/lib/mix/tasks/deps.update.ex +++ b/lib/mix/lib/mix/tasks/deps.update.ex @@ -16,13 +16,16 @@ defmodule Mix.Tasks.Deps.Update do * `--no-compile` skip compilation of dependencies """ - import Mix.Deps, only: [all: 2, available?: 1, by_name!: 1, format_dep: 1] + import Mix.Deps, only: [ all: 0, all: 2, available?: 1, by_name!: 2, + depending: 2, format_dep: 1 ] def run(args) do { opts, rest } = OptionParser.parse(args, switches: [no_compile: :boolean]) if rest != [] do - deps = Enum.map by_name!(rest), check_unavailable!(&1) + all_deps = all + deps = Enum.map by_name!(rest, all_deps), check_unavailable!(&1) + deps = deps ++ depending(deps, all_deps) { _, acc } = Enum.map_reduce deps, init, deps_updater(&1, &2) else acc = all(init, deps_updater(&1, &2)) diff --git a/lib/mix/test/mix/tasks/deps_test.exs b/lib/mix/test/mix/tasks/deps_test.exs index ce0fa4730f5..d73265348a8 100644 --- a/lib/mix/test/mix/tasks/deps_test.exs +++ b/lib/mix/test/mix/tasks/deps_test.exs @@ -257,7 +257,7 @@ defmodule Mix.Tasks.DepsTest do assert_received { :mix_shell, :info, ["Generated git_repo.app"] } Mix.Tasks.Deps.Update.run [] - assert_received { :mix_shell, :info, ["* Updating deps_repo [path: \"custom/deps_repo\"]"] } + assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] } end after purge [GitRepo, GitRepo.Mix, DepsRepo] @@ -290,8 +290,9 @@ defmodule Mix.Tasks.DepsTest do Mix.Task.clear Mix.Tasks.Deps.Update.run [] - assert_received { :mix_shell, :info, ["* Updating deps_repo [path: \"custom/deps_repo\"]"] } - assert_received { :mix_shell, :info, ["* Updating bad_deps_repo [path: \"custom/bad_deps_repo\"]"] } + + assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] } + assert_received { :mix_shell, :info, ["* Updating bad_deps_repo (0.1.0) [path: \"custom/bad_deps_repo\"]"] } Mix.Tasks.Deps.Check.run [] end @@ -312,4 +313,21 @@ defmodule Mix.Tasks.DepsTest do purge [GitRepo, GitRepo.Mix, DepsRepo, BadDepsRepo] Mix.Project.pop end -end \ No newline at end of file + + test "update parent dependencies" do + Mix.Project.push NestedDepsApp + + in_fixture "deps_status", fn -> + Mix.Tasks.Deps.Get.run [] + Mix.Task.clear + Mix.Tasks.Deps.Update.run ["git_repo"] + + message = "* Updating git_repo (0.1.0) [git: \"#{fixture_path("git_repo")}\"]" + assert_received { :mix_shell, :info, [^message] } + assert_received { :mix_shell, :info, ["* Updating deps_repo (0.1.0) [path: \"custom/deps_repo\"]"] } + end + after + purge [GitRepo, GitRepo.Mix, DepsRepo] + Mix.Project.pop + end +end