View Source mix xref (Mix v1.13.4)
Prints cross reference information between modules.
The xref
task expects a mode as first argument:
$ mix xref MODE
All available modes are discussed below.
This task is automatically reenabled, so you can print information multiple times in the same Mix invocation.
mix xref callers MODULE
Prints all callers of the given MODULE
. Example:
$ mix xref callers MyMod
mix xref trace FILE
Compiles the given file listing all dependencies within the same app. It includes the type and line for each one. Example:
$ mix xref trace lib/my_app/router.ex
The --label
option may be given to keep only certain traces
(compile, runtime or export):
$ mix xref trace lib/my_app/router.ex --label compile
If you have an umbrella application, we also recommend using the
--include-siblings
flag to see the dependencies on other
umbrella applications.
Example
Imagine the given file lib/b.ex:
defmodule B do
import A
A.macro()
macro()
A.fun()
fun()
def calls_macro, do: A.macro()
def calls_fun, do: A.fun()
def calls_struct, do: %A{}
end
mix xref trace
will print:
lib/b.ex:2: require A (export)
lib/b.ex:3: call A.macro/0 (compile)
lib/b.ex:4: import A.macro/0 (compile)
lib/b.ex:5: call A.fun/0 (compile)
lib/b.ex:6: call A.fun/0 (compile)
lib/b.ex:6: import A.fun/0 (compile)
lib/b.ex:7: call A.macro/0 (compile)
lib/b.ex:8: call A.fun/0 (runtime)
lib/b.ex:9: struct A (export)
mix xref graph
Prints a file dependency graph where an edge from A
to B
indicates
that A
(source) depends on B
(sink).
$ mix xref graph --format stats
The following options are accepted:
--exclude
- paths to exclude--label
- only shows relationships with the given label. The labels are "compile", "export" and "runtime". By default, the--label
option simply filters the printed graph to show only relationships with the given label. If you want to effectively filter the graph, you can pass the--only-direct
flag. There is also a special label called "compile-connected" that keeps only compile-time files with at least one transitive dependency. See "Dependencies types" section below.--only-direct
- keeps only files with the direct relationship given by--label
--only-nodes
- only shows the node names (no edges). Generally useful with the--sink
flag--source
- displays all files that the given source file references (directly or indirectly)--sink
- displays all files that reference the given file (directly or indirectly)--min-cycle-size
- controls the minimum cycle size on formats likestats
andcycles
--format
- can be set to one of:pretty
- prints the graph to the terminal using Unicode characters. Each prints each file followed by the files it depends on. This is the default except on Windows;plain
- the same as pretty except ASCII characters are used instead of Unicode characters. This is the default on Windows;stats
- prints general statistics about the graph;cycles
- prints all cycles in the graph;dot
- produces a DOT graph description inxref_graph.dot
in the current directory. Warning: this will override any previously generated file
The --source
and --sink
options are particularly useful when trying to understand
how the modules in a particular file interact with the whole system. You can combine
those options with --label
and --only-nodes
to get all files that exhibit a certain
property, for example:
# To show all compile-time relationships
mix xref graph --label compile
# To get the tree that depend on lib/foo.ex at compile time
mix xref graph --label compile --sink lib/foo.ex
# To get all files that depend on lib/foo.ex at compile time
mix xref graph --label compile --sink lib/foo.ex --only-nodes
# To get all paths between two files
mix xref graph --source lib/foo.ex --sink lib/bar.ex
# To show general statistics about the graph
mix xref graph --format stats
Understanding the printed graph
When mix xref graph
runs, it will print a tree of the following
format. Imagine the following code:
# lib/a.ex
defmodule A do
IO.puts B.hello()
end
# lib/b.ex
defmodule B do
def hello, do: C.world()
end
# lib/c.ex
defmodule C do
def world, do: "hello world"
end
It will print:
$ mix xref graph
lib/a.ex
└── lib/b.ex (compile)
lib/b.ex
└── lib/c.ex
lib/c.ex
This tree means that lib/a.ex
depends on lib/b.ex
at compile
time. And lib/b.ex
depends on lib/c.ex
at runtime. This is often
problematic because if lib/c.ex
changes, lib/a.ex
also has to
recompile due to this indirect compile time dependency. When you pass
--label compile
, the graph shows only the compile-time dependencies:
$ mix xref graph --label compile
lib/a.ex
└── lib/b.ex (compile)
The --label compile
flag removes all non-compile dependencies. However,
this can be misleading because having direct compile time dependencies is
not necessarily an issue. The biggest concern, as mentioned above, are the
transitive compile time dependencies. You can get all compile time
dependencies that cause transitive compile time dependencies by using
--label compile-connected
:
$ mix xref graph --label compile-connected
lib/a.ex
└── lib/b.ex (compile)
The above says lib/a.ex
depends on lib/b.ex
and that causes transitive
compile time dependencies - as we know, lib/a.ex
also depends on lib/c.ex
.
We can retrieve those transitive dependencies by passing lib/b.ex
as
--source
to mix xref graph
:
$ mix xref graph --source lib/b.ex
lib/b.ex
└── lib/c.ex
Similarly, you can use the --label compile
and the --sink
flag to find
all compile time dependencies that will recompile once the sink changes:
$ mix xref graph --label compile --sink lib/c.ex
lib/a.ex
└── lib/b.ex (compile)
Dependencies types
Elixir tracks three types of dependencies between modules: compile,
exports, and runtime. If a module has a compile time dependency on
another module, the caller module has to be recompiled whenever the
callee changes. Compile-time dependencies are typically added when
using macros or when invoking functions in the module body (outside
of functions). You can list all dependencies in a file by running
mix xref trace path/to/file.ex
.
Exports dependencies are compile time dependencies on the module API, namely structs and its public definitions. For example, if you import a module but only use its functions, it is an export dependency. If you use a struct, it is an export dependency too. Export dependencies are only recompiled if the module API changes. Note, however, that compile time dependencies have higher precedence than exports. Therefore if you import a module and use its macros, it is a compile time dependency.
Runtime dependencies are added whenever you invoke another module
inside a function. Modules with runtime dependencies do not have
to be compiled when the callee changes, unless there is a transitive
compile or an outdated export time dependency between them. The option
--label compile-connected
can be used to find the first case.
Shared options
Those options are shared across all modes:
--fail-above
- generates a failure if the relevant metric is above the given threshold. This metric is the number of references, except for--format cycles
where it is the number of cycles, and--format stats
which has none.--include-siblings
- includes dependencies that have:in_umbrella
set to true in the current project in the reports. This can be used to find callers or to analyze graphs between projects--no-compile
- does not compile even if files require compilation--no-deps-check
- does not check dependencies--no-archives-check
- does not check archives--no-elixir-version-check
- does not check the Elixir version from mix.exs
Link to this section Summary
Functions
Returns a list of information of all the runtime function calls in the project.
Link to this section Functions
@spec calls(keyword()) :: [ %{callee: {module(), atom(), arity()}, line: integer(), file: String.t()} ]
Returns a list of information of all the runtime function calls in the project.
Each item in the list is a map with the following keys:
:callee
- a tuple containing the module, function, and arity of the call:line
- an integer representing the line where the function is called:file
- a binary representing the file where the function is called:caller_module
- the module where the function is called
This function returns an empty list when used at the root of an umbrella project because there is no compile manifest to extract the function call information from. To get the function calls of each child in an umbrella, execute the function at the root of each individual application.