Skip to content

Commit 7ecb53c

Browse files
author
m.malkin
committed
implemented reading code graph of deps from compiler manifests
1 parent 202010c commit 7ecb53c

File tree

12 files changed

+269
-7
lines changed

12 files changed

+269
-7
lines changed

.formatter.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
4+
line_length: 120
5+
]

.gitignore

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1-
/_build
2-
/cover
3-
/deps
4-
/doc
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
514
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
617
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
720
*.ez
8-
*.beam
9-
/config/*.secret.exs
10-
.elixir_ls/
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
clean_mixer-*.tar
24+

lib/clean_mixer.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule CleanMixer do
2+
@moduledoc """
3+
Documentation for CleanMixer.
4+
"""
5+
6+
@doc """
7+
Hello world.
8+
9+
## Examples
10+
11+
iex> CleanMixer.hello()
12+
:world
13+
14+
"""
15+
def hello do
16+
:world
17+
end
18+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule CleanMixer.CodeGraph.Dependency do
2+
alias CleanMixer.CodeGraph.SourceFile
3+
4+
@type type :: :compile | :struct | :runtime
5+
6+
defstruct [:source, :target, :type]
7+
8+
@type t :: %__MODULE__{
9+
source: SourceFile.t(),
10+
target: SourceFile.t(),
11+
type: type
12+
}
13+
14+
@spec new(SourceFile.t(), SourceFile.t(), type) :: t
15+
def new(source, target, type) do
16+
%__MODULE__{source: source, target: target, type: type}
17+
end
18+
19+
defimpl String.Chars do
20+
def to_string(dep),
21+
do: "#{dep.source.path} -> #{dep.target.path} (#{dep.type})"
22+
end
23+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule CleanMixer.CodeGraph.SourceFile do
2+
defstruct [:path]
3+
4+
@type t :: %__MODULE__{
5+
path: Path.t()
6+
}
7+
8+
@spec new(Path.t()) :: t
9+
def new(path) do
10+
%__MODULE__{path: path}
11+
end
12+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
defmodule CleanMixer.CompilerManifests.App do
2+
alias __MODULE__
3+
4+
@manifest_filename "compile.elixir"
5+
6+
defstruct [:path, :name, :manifest_path]
7+
8+
@type t :: %{
9+
path: Path.t(),
10+
name: :atom,
11+
manifest_path: Path.t()
12+
}
13+
14+
def project_apps() do
15+
if Mix.Project.umbrella?() do
16+
umbrella_apps()
17+
else
18+
[current_app()]
19+
end
20+
end
21+
22+
defp umbrella_apps do
23+
for %{app: app_name, scm: Mix.SCM.Path, opts: opts} <- Mix.Dep.cached(), opts[:from_umbrella] do
24+
%App{
25+
path: opts[:path],
26+
name: app_name,
27+
manifest_path: Path.join([opts[:build], ".mix", @manifest_filename])
28+
}
29+
end
30+
end
31+
32+
defp current_app() do
33+
%App{
34+
path: "",
35+
name: Mix.Project.config() |> Keyword.fetch!(:app),
36+
manifest_path: Mix.Project.manifest_path() |> Path.join(@manifest_filename)
37+
}
38+
end
39+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule CleanMixer.Xref.CodeGraphSource do
2+
alias CleanMixer.CompilerManifests.App
3+
alias CleanMixer.CompilerManifests.Manifest
4+
5+
alias CleanMixer.CodeGraph.Dependency
6+
alias CleanMixer.CodeGraph.SourceFile
7+
8+
def fetch(_options \\ []) do
9+
App.project_apps()
10+
|> IO.inspect()
11+
|> Enum.flat_map(&fetch_for_app/1)
12+
|> Enum.reject(&is_nil/1)
13+
|> Enum.uniq()
14+
end
15+
16+
defp fetch_for_app(%App{path: app_path} = app) do
17+
manifest = Manifest.read_manifest(app)
18+
19+
for %Manifest.SourceFile{path: path, refs: refs} <- manifest.source_files,
20+
%Manifest.Reference{module_name: module_name, type: type} <- refs do
21+
callee_module = Manifest.module_for_name(manifest, module_name)
22+
23+
if callee_module != nil do
24+
caller_path = Path.join(app_path, path)
25+
callee_path = Path.join(app_path, callee_module.path)
26+
Dependency.new(SourceFile.new(caller_path), SourceFile.new(callee_path), type)
27+
end
28+
end
29+
end
30+
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
defmodule CleanMixer.CompilerManifests.Manifest do
2+
alias CleanMixer.CompilerManifests.App
3+
require Mix.Compilers.Elixir, as: Compiler
4+
alias __MODULE__
5+
6+
@type module_name :: module()
7+
8+
defmodule Module do
9+
defstruct [:name, :path]
10+
11+
@type t :: %__MODULE__{
12+
name: Manifest.module_name(),
13+
path: Path.t()
14+
}
15+
end
16+
17+
defmodule Reference do
18+
defstruct [:module_name, :type]
19+
20+
@type ref_type :: :runtime | :struct | :compile
21+
22+
@type t :: %__MODULE__{
23+
module_name: Manifest.module_name(),
24+
type: ref_type
25+
}
26+
end
27+
28+
defmodule SourceFile do
29+
defstruct [:path, :refs]
30+
31+
@type t :: %__MODULE__{
32+
path: Path.t(),
33+
refs: list(Reference.t())
34+
}
35+
end
36+
37+
defstruct [:modules, :source_files]
38+
39+
@type t :: %__MODULE__{
40+
modules: list(Module.t()),
41+
source_files: list(SourceFile.t())
42+
}
43+
44+
def read_manifest(%App{manifest_path: path}) do
45+
items = Compiler.read_manifest(path, "")
46+
47+
%Manifest{
48+
modules: manifest_modules(items),
49+
source_files: manifest_sources(items)
50+
}
51+
end
52+
53+
@spec module_for_name(t(), module_name) :: Module.t() | nil
54+
def module_for_name(%Manifest{modules: modules}, module_name) do
55+
Enum.find(modules, &(&1.name == module_name))
56+
end
57+
58+
defp manifest_modules(items) do
59+
for Compiler.module(sources: [path | _], module: name) <- items do
60+
%Module{name: name, path: path}
61+
end
62+
end
63+
64+
defp manifest_sources(items) do
65+
for Compiler.source(
66+
source: path,
67+
compile_references: compile_references,
68+
struct_references: struct_references,
69+
runtime_references: runtime_references
70+
) <- items do
71+
references =
72+
references_of(:compile, compile_references) ++
73+
references_of(:runtime, runtime_references) ++
74+
references_of(:struct, struct_references)
75+
76+
%SourceFile{path: path, refs: references}
77+
end
78+
end
79+
80+
defp references_of(type, modules) do
81+
modules |> Enum.map(&%Reference{module_name: &1, type: type})
82+
end
83+
end

mix.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule CleanMixer.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :clean_mixer,
7+
version: "0.1.0",
8+
elixir: "~> 1.9",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
def application do
15+
[
16+
extra_applications: [:logger, :mix]
17+
]
18+
end
19+
20+
defp deps do
21+
[{:dialyxir, "~> 1.0.0-rc.7", only: [:dev, :test], runtime: false}]
22+
end
23+
end

mix.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
%{
2+
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
3+
"erlang_dot": {:git, "https://github.com/fenollp/erlang-dot.git", "8f2e22e4aa425220e928d669fa6a08d3df1fd4a6", []},
4+
"erlex": {:hex, :erlex, "0.2.5", "e51132f2f472e13d606d808f0574508eeea2030d487fc002b46ad97e738b0510", [:mix], [], "hexpm"},
5+
"graphvix": {:git, "https://github.com/fenollp/erlang-dot.git", "8f2e22e4aa425220e928d669fa6a08d3df1fd4a6", []},
6+
}

test/clean_mixer_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule CleanMixerTest do
2+
use ExUnit.Case
3+
doctest CleanMixer
4+
5+
test "greets the world" do
6+
assert CleanMixer.hello() == :world
7+
end
8+
end

test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)