Permalink
Browse files

Add mix deps.tree

  • Loading branch information...
1 parent 0db3a62 commit 8ff3275f3e3417de084a90daca1bfd2e59e2d6dd @josevalim josevalim committed Feb 4, 2016
@@ -13,7 +13,7 @@ defmodule Mix.Tasks.App.Tree do
## Command line options
- * `--exclude` - exclude dependencies which you do not want to see printed.
+ * `--exclude` - exclude applications which you do not want to see printed.
`kernel`, `stdlib` and `compiler` are always excluded from the tree.
* `--pretty` - use Unicode codepoints for formatting the tree.
@@ -41,24 +41,21 @@ defmodule Mix.Tasks.App.Tree do
Mix.Utils.print_tree([{:normal, app}], fn {type, app} ->
load(app)
- if app in excluded do
- false
- else
- {"#{app}#{type(type)}", children_for(app)}
- end
+ {"#{app}#{type(type)}", children_for(app, excluded)}
end, opts)
end
defp load(app) do
case Application.load(app) do
:ok -> :ok
{:error, {:already_loaded, ^app}} -> :ok
+ _ -> Mix.raise("could not find application #{app}")
end
end
- defp children_for(app) do
- apps = Application.spec(app, :applications)
- included_apps = Application.spec(app, :included_applications)
+ defp children_for(app, excluded) do
+ apps = Application.spec(app, :applications) -- excluded
+ included_apps = Application.spec(app, :included_applications) -- excluded
Enum.map(apps, &{:normal, &1}) ++ Enum.map(included_apps, &{:included, &1})
end
@@ -0,0 +1,76 @@
+defmodule Mix.Tasks.Deps.Tree do
+ use Mix.Task
+
+ @shortdoc "Prints the dependency tree"
+
+ @moduledoc """
+ Prints the dependency tree.
+
+ mix deps.tree
+
+ If no dependency is given, it uses the tree defined in the `mix.exs` file.
+
+ ## Command line options
+
+ * `--only` - the enviroment to show dependencies for
+
+ * `--exclude` - exclude dependencies which you do not want to see printed.
+
+ * `--pretty` - use Unicode codepoints for formatting the tree.
+ Defaults to true except on Windows.
+
+ """
+ @switches [only: :string, exclude: :keep, pretty: :boolean]
+
+ @spec run(OptionParser.argv) :: :ok
+ def run(args) do
+ Mix.Project.get!
+ {opts, args, _} = OptionParser.parse(args, switches: @switches)
+
+ deps_opts = if only = opts[:only], do: [env: :"#{only}"], else: []
+ deps = Mix.Dep.loaded(deps_opts)
+ excluded = Keyword.get_values(opts, :exclude) |> Enum.map(&String.to_atom/1)
+ top_level = Enum.filter(deps, & &1.top_level)
+
+ root =
+ case args do
+ [] ->
+ Mix.Project.config[:app] || Mix.raise("no application given and none found in mix.exs file")
+ [app] ->
+ app = String.to_atom(app)
+ find_dep(deps, app) || Mix.raise("could not find dependency #{app}")
+ end
+
+ Mix.Utils.print_tree([root], fn
+ %Mix.Dep{app: app} = dep ->
+ deps =
+ # Do not show dependencies if they were
+ # already show at the top level
+ if not dep.top_level && find_dep(top_level, app) do
+ []
+ else
+ find_dep(deps, app).deps
+ end
+ {format_dep(dep), exclude(deps, excluded)}
+ app ->
+ {Atom.to_string(app), exclude(top_level, excluded)}
+ end, opts)
+ end
+
+ defp exclude(deps, excluded) do
+ Enum.reject deps, & &1.app in excluded
+ end
+
+ defp format_dep(%{app: app, scm: scm, requirement: requirement, opts: opts}) do
+ override = if opts[:override], do: "#{IO.ANSI.bright} *override*#{IO.ANSI.normal}", else: ""
+ "#{app}#{requirement(requirement)} (#{scm.format(opts)})#{override}"
+ end
+
+ defp requirement(nil), do: ""
+ defp requirement(%Regex{} = regex), do: " #{inspect regex}"
+ defp requirement(binary) when is_binary(binary), do: " #{binary}"
+
+ defp find_dep(deps, app) do
+ Enum.find(deps, & &1.app == app)
+ end
+end
@@ -143,21 +143,17 @@ defmodule Mix.Utils do
must either return `{printed, children}` tuple or
`false` if the given node must not be printed.
"""
- @spec print_tree([term], (term -> {String.t, [term]} | false), Keyword.t) :: :ok
+ @spec print_tree([term], (term -> {String.t, [term]}), Keyword.t) :: :ok
def print_tree(nodes, callback, opts \\ []) do
pretty = Keyword.get(opts, :pretty, elem(:os.type, 0) != :win32)
print_tree(nodes, [], pretty, callback)
end
defp print_tree([], _depth, _pretty, _callback), do: :ok
defp print_tree([node | nodes], depth, pretty, callback) do
- case callback.(node) do
- {print, children} ->
- Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{print}")
- print_tree(children, [(nodes != []) | depth], pretty, callback)
- false ->
- :ok
- end
+ {print, children} = callback.(node)
+ Mix.shell.info("#{depth(pretty, depth)}#{prefix(pretty, depth, nodes)}#{print}")
+ print_tree(children, [(nodes != []) | depth], pretty, callback)
print_tree(nodes, depth, pretty, callback)
end
@@ -34,6 +34,10 @@ defmodule Mix.Tasks.App.TreeTest do
Mix.Project.push AppDepsSample
in_tmp context.test, fn ->
+ assert_raise Mix.Error, "could not find application app_deps_sample", fn ->
+ Mix.Tasks.App.Tree.run(["--pretty", "app_deps_sample"])
+ end
+
load_apps()
Mix.Tasks.App.Tree.run(["--pretty", "app_deps_sample"])
@@ -54,7 +58,7 @@ defmodule Mix.Tasks.App.TreeTest do
assert_received {:mix_shell, :info, ["test"]}
assert_received {:mix_shell, :info, ["└── app_deps_sample"]}
- assert_received {:mix_shell, :info, [" ── app_deps2_sample"]}
+ assert_received {:mix_shell, :info, [" ── app_deps2_sample"]}
refute_received {:mix_shell, :info, [" │ └── app_deps4_sample (included)"]}
refute_received {:mix_shell, :info, [" └── app_deps3_sample"]}
end
@@ -64,6 +68,6 @@ defmodule Mix.Tasks.App.TreeTest do
:ok = :application.load({:application, :app_deps4_sample, [vsn: '1.0.0', env: []]})
:ok = :application.load({:application, :app_deps3_sample, [vsn: '1.0.0', env: []]})
:ok = :application.load({:application, :app_deps2_sample, [vsn: '1.0.0', env: [], included_applications: [:app_deps4_sample]]})
- :ok = :application.load({:application, :app_deps_sample, [vsn: '1.0.0', env: [], applications: [:app_deps2_sample, :app_deps3_sample]]})
+ :ok = :application.load({:application, :app_deps_sample, [vsn: '1.0.0', env: [], applications: [:app_deps2_sample, :app_deps3_sample]]})
end
end
@@ -0,0 +1,98 @@
+Code.require_file "../../test_helper.exs", __DIR__
+
+defmodule Mix.Tasks.Deps.TreeTest do
+ use MixTest.Case
+
+ defmodule ConvergedDepsApp do
+ def project do
+ [
+ app: :sample,
+ version: "0.1.0",
+ deps: [
+ {:deps_on_git_repo, "0.2.0", git: fixture_path("deps_on_git_repo")},
+ {:git_repo, ">= 0.1.0", git: MixTest.Case.fixture_path("git_repo")}
+ ]
+ ]
+ end
+ end
+
+ defmodule OverridenDepsApp do
+ def project do
+ [
+ app: :sample,
+ version: "0.1.0",
+ deps: [
+ {:deps_on_git_repo, ~r"0.2.0", git: fixture_path("deps_on_git_repo"), only: :test},
+ {:git_repo, git: MixTest.Case.fixture_path("git_repo"), override: true}
+ ]
+ ]
+ end
+ end
+
+ test "shows the dependency tree", context do
+ Mix.Project.push ConvergedDepsApp
+
+ in_tmp context.test, fn ->
+ Mix.Tasks.Deps.Tree.run(["--pretty"])
+ assert_received {:mix_shell, :info, ["sample"]}
+ assert_received {:mix_shell, :info, ["├── git_repo >= 0.1.0 (" <> _]}
+ assert_received {:mix_shell, :info, ["└── deps_on_git_repo 0.2.0 (" <> _]}
+ refute_received {:mix_shell, :info, [" └── git_repo (" <> _]}
+
+ Mix.Tasks.Deps.Get.run([])
+ Mix.Tasks.Deps.Tree.run(["--pretty"])
+ assert_received {:mix_shell, :info, ["sample"]}
+ assert_received {:mix_shell, :info, ["├── git_repo >= 0.1.0 (" <> _]}
+ assert_received {:mix_shell, :info, ["└── deps_on_git_repo 0.2.0 (" <> _]}
+ assert_received {:mix_shell, :info, [" └── git_repo (" <> _]}
+ end
+ end
+
+ test "shows the given dependency", context do
+ Mix.Project.push ConvergedDepsApp
+
+ in_tmp context.test, fn ->
+ assert_raise Mix.Error, "could not find dependency unknown", fn ->
+ Mix.Tasks.Deps.Tree.run(["--pretty", "unknown"])
+ end
+
+ Mix.Tasks.Deps.Tree.run(["--pretty", "deps_on_git_repo"])
+ assert_received {:mix_shell, :info, ["deps_on_git_repo 0.2.0 (" <> _]}
+ refute_received {:mix_shell, :info, ["└── git_repo (" <> _]}
+ end
+ end
+
+ test "shows overriden deps", context do
+ Mix.Project.push OverridenDepsApp
+
+ in_tmp context.test, fn ->
+ Mix.Tasks.Deps.Tree.run(["--pretty"])
+ assert_received {:mix_shell, :info, ["sample"]}
+ assert_received {:mix_shell, :info, ["├── git_repo (" <> msg]}
+ assert_received {:mix_shell, :info, ["└── deps_on_git_repo ~r/0.2.0/ (" <> _]}
+ assert msg =~ "*override*"
+ end
+ end
+
+ test "excludes the given deps", context do
+ Mix.Project.push OverridenDepsApp
+
+ in_tmp context.test, fn ->
+ Mix.Tasks.Deps.Tree.run(["--pretty", "--exclude", "deps_on_git_repo"])
+ assert_received {:mix_shell, :info, ["sample"]}
+ assert_received {:mix_shell, :info, ["└── git_repo (" <> _]}
+ refute_received {:mix_shell, :info, ["└── deps_on_git_repo ~r/0.2.0/ (" <> _]}
+ end
+ end
+
+ test "shows a particular environment", context do
+ Mix.Project.push OverridenDepsApp
+
+ in_tmp context.test, fn ->
+ Mix.Tasks.Deps.Tree.run(["--pretty", "--only", "prod"])
+ assert_received {:mix_shell, :info, ["sample"]}
+ assert_received {:mix_shell, :info, ["└── git_repo (" <> _]}
+ refute_received {:mix_shell, :info, ["└── deps_on_git_repo ~r/0.2.0/ (" <> _]}
+ end
+ end
+end

0 comments on commit 8ff3275

Please sign in to comment.