Packages

A toolkit to work with umbrella projects

Current section

Files

Jump to
brolly lib brolly.ex
Raw

lib/brolly.ex

defmodule Brolly do
@moduledoc """
Documentation for Brolly.
"""
def dep_tree!(app \\ nil, base_dir \\ ".")
def dep_tree!(nil, base_dir) do
base_dir
|> Path.join("apps")
|> File.ls!()
|> Stream.map(&Path.basename/1)
|> Stream.map(&String.to_atom/1)
|> Enum.map(&{&1, dep_tree!(&1, base_dir)})
end
def dep_tree!(app, base_dir) when is_atom(app) do
Mix.Project.in_project(app, "#{base_dir}/apps/#{app}", fn(_module) ->
[]
|> Mix.Dep.load_on_environment()
|> filter_dep_tree([])
|> app_names([])
end)
end
defp filter_dep_tree([], acc), do: acc
defp filter_dep_tree([dep | tail], acc) do
if umbrella_dep?(dep) do
dep = %{dep | deps: filter_dep_tree(dep.deps, [])}
filter_dep_tree(tail, [dep | acc])
else
filter_dep_tree(tail, acc)
end
end
defp app_names([], acc), do: acc
defp app_names([%Mix.Dep{app: app, deps: []} | tail], acc) do
app_names(tail, [app | acc])
end
defp app_names([%Mix.Dep{app: app, deps: deps} | tail], acc) do
app_names(tail, [{app, app_names(deps, [])} | acc])
end
def reverse_dep_tree!(app, base_dir \\ ".") when is_atom(app) do
# TODO - this is very slow for big project, since we need to go
# through all directories and load each project. Unfortunately, there is no
# other way to do it with the current version of mix, other than rewriting
# the deps load.
base_dir
|> File.ls!()
|> Stream.filter(&File.dir?(Path.join(base_dir, &1)))
|> Stream.map(&String.to_atom/1)
|> Stream.filter(&depends_from?(&1, app, base_dir))
|> Stream.map(fn(dep) ->
case reverse_dep_tree!(dep, base_dir) do
[] -> dep
deps -> {dep, deps}
end
end)
|> Enum.to_list()
end
defp depends_from?(%Mix.Dep{deps: left_deps}, right, _) do
Enum.any?(left_deps, &(&1.app == right))
end
defp depends_from?(left, right, base_dir) when is_atom(left) and is_atom(right) do
Mix.Project.in_project(left, "#{base_dir}/#{left}", fn(_module) ->
[]
|> Mix.Dep.load_on_environment()
|> Enum.any?(&(&1.app == right))
end)
end
defp umbrella_dep?(%Mix.Dep{app: name, scm: Mix.SCM.Path, opts: opts}) do
opts[:path] == "../#{name}"
end
defp umbrella_dep?(%Mix.Dep{}), do: false
def affected_projects(sha, base_dir \\ ".", git_root \\ :base_dir) do
sha
|> changed_projects(base_dir, git_root)
|> Stream.map(&reverse_dep_tree!(&1, base_dir))
|> Enum.uniq()
end
def changed_projects(sha, base_dir \\ ".", git_root \\ :base_dir) do
base_dir
|> Git.new()
|> Git.diff_tree!(["--no-commit-id", "--name-only", "-r", sha])
|> String.split("\n")
|> handle_git_root(base_dir, git_root)
|> Stream.map(&List.first(String.split(&1, "/")))
|> Stream.map(&String.slice(&1, 0, String.length(&1)))
|> Stream.filter(&File.dir?(Path.join(base_dir, &1)))
|> Stream.map(&String.to_atom/1)
|> Enum.uniq()
end
defp handle_git_root(lines, _base_dir, :base_dir), do: lines
defp handle_git_root(lines, base_dir, git_root) do
subdir = String.replace(Path.expand(base_dir), Path.expand(git_root) <> "/", "")
lines
|> Stream.filter(&String.starts_with?(&1, "#{subdir}/"))
|> Stream.map(&String.replace(&1, "#{subdir}/", ""))
end
end