Skip to content

Commit 228c332

Browse files
committed
Print strong connected cycles in mix xref
Prior to this version, Elixir was breaking strong connected cycles in smaller cycles, but without considering all possible subcycles, which meant cycles with compile-time deps in them could be missed. This commit ensures we only print the strong connected cycle, highlighting vertices with compile deps in them.
1 parent d25bea5 commit 228c332

File tree

2 files changed

+31
-37
lines changed

2 files changed

+31
-37
lines changed

lib/mix/lib/mix/tasks/xref.ex

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ defmodule Mix.Tasks.Xref do
250250
251251
* `stats` - prints general statistics about the graph;
252252
253-
* `cycles` - prints all cycles in the graph;
253+
* `cycles` - prints all strongly connected cycles in the graph;
254254
255255
* `dot` - produces a DOT graph description in `xref_graph.dot` in the
256256
current directory. Warning: this will override any previously generated file
@@ -366,27 +366,20 @@ defmodule Mix.Tasks.Xref do
366366
367367
lib/c.ex
368368
lib/b.ex
369-
lib/a.ex
370-
371-
The cycles are given in order: `c.ex` depends on `b.ex` which depends
372-
on `a.ex` which depends on `c.ex`. In particular, you want to avoid
373-
cycles with compile dependencies in there. You can find those cycles
374-
with:
369+
lib/a.ex (compile)
370+
371+
More precisely, `xref` is printing strongly connected cycles, which
372+
is the largest cycle possible involving these files. For this reason,
373+
files may have multiple relationships between them, and therefore the
374+
cycles are not printed in order. The label reflects the highest type
375+
of relationship between the given file and any other file in the cycle.
376+
In the example above, it means `lib/a.ex` depends on something else in
377+
the cycle at compile-time. Those are exactly the type of dependencies
378+
we want to avoid, and you can ask `mix xref` to only print graphs with
379+
with compile dependencies in them by passing the `--label` flag:
375380
376381
$ mix xref graph --format cycles --label compile-connected
377382
378-
Which may look like this:
379-
380-
Cycle of length 3:
381-
382-
lib/c.ex
383-
lib/b.ex (compile)
384-
lib/a.ex
385-
386-
This means `c.ex` depends on `b.ex` at compile time. Any compile dependency
387-
in a cycle is by definition a compile-connected dependency, which must be
388-
generally avoided, as explained earlier in the module documentation.
389-
390383
## Shared options
391384
392385
Those options are shared across all modes:
@@ -1181,8 +1174,7 @@ defmodule Mix.Tasks.Xref do
11811174
cycles =
11821175
graph
11831176
|> :digraph_utils.cyclic_strong_components()
1184-
|> Enum.reduce([], &inner_cycles(graph, Enum.sort(&1), &2))
1185-
|> Enum.map(&{length(&1), &1})
1177+
|> Enum.map(&{length(&1), add_labels(&1, graph)})
11861178

11871179
cycles =
11881180
if min = opts[:min_cycle_size] do
@@ -1204,20 +1196,22 @@ defmodule Mix.Tasks.Xref do
12041196
defp cycle_filter_fn(:compile_connected), do: cycle_filter_fn(:compile)
12051197
defp cycle_filter_fn(filter), do: fn {_node, type} -> type == filter end
12061198

1207-
defp inner_cycles(_graph, [], acc), do: acc
1208-
1209-
defp inner_cycles(graph, [v | vertices], acc) do
1210-
cycle = :digraph.get_cycle(graph, v)
1211-
inner_cycles(graph, vertices -- cycle, [label_cycle(cycle, graph) | acc])
1199+
defp add_labels(vertices, graph) do
1200+
vertices
1201+
|> Enum.map(fn v -> {v, cycle_label(vertices, graph, v, false)} end)
1202+
|> Enum.sort()
12121203
end
12131204

1214-
defp label_cycle([from, to | cycle], graph) do
1215-
{_edge, _v1, _v2, label} = :digraph.edge(graph, {from, to})
1216-
[{to, label} | label_cycle([to | cycle], graph)]
1205+
defp cycle_label([out | outs], graph, v, export?) do
1206+
case :digraph.edge(graph, {v, out}) do
1207+
{_, _, _, :compile} -> :compile
1208+
{_, _, _, :export} -> cycle_label(outs, graph, v, true)
1209+
_ -> cycle_label(outs, graph, v, export?)
1210+
end
12171211
end
12181212

1219-
defp label_cycle([_from], _graph) do
1220-
[]
1213+
defp cycle_label([], _graph, _v, export?) do
1214+
if export?, do: :export, else: nil
12211215
end
12221216

12231217
defp print_cycles(references, filter, opts) do

lib/mix/test/mix/tasks/xref_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,8 @@ defmodule Mix.Tasks.XrefTest do
502502
503503
Cycle of length 2:
504504
505-
lib/b.ex (compile)
506-
lib/a.ex
505+
lib/a.ex (compile)
506+
lib/b.ex
507507
508508
""")
509509
end
@@ -514,8 +514,8 @@ defmodule Mix.Tasks.XrefTest do
514514
515515
Cycle of length 2:
516516
517-
lib/b.ex (compile)
518-
lib/a.ex
517+
lib/a.ex (compile)
518+
lib/b.ex
519519
520520
""")
521521
end
@@ -526,8 +526,8 @@ defmodule Mix.Tasks.XrefTest do
526526
527527
Cycle of length 2:
528528
529-
lib/b.ex (compile)
530-
lib/a.ex
529+
lib/a.ex (compile)
530+
lib/b.ex
531531
532532
""")
533533
end

0 commit comments

Comments
 (0)