Skip to content

Commit 7758084

Browse files
committed
Add --min-cycle-label to mix xref graph
1 parent 57d3098 commit 7758084

File tree

2 files changed

+87
-20
lines changed

2 files changed

+87
-20
lines changed

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

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,22 @@ defmodule Mix.Tasks.Xref do
4444
└── lib/a.ex
4545
4646
Because you have a compile-time dependency, any of the files `lib/a.ex`,
47-
`lib/b.ex`, and `lib/c.ex` depend on will cause the whole cycle to
48-
recompile. Therefore, your first priority to reduce compile times is
49-
to remove such cycles. You can spot them by running:
47+
`lib/b.ex`, and `lib/c.ex` depend on will cause `lib/a.ex` to recompile.
48+
In other words, whenever you have a cycle, **a change to any file in the
49+
cycle will cause all compile-time deps to recompile**. Therefore, your
50+
first priority to reduce constant recompilations is to remve them.
51+
You can spot them by running:
5052
5153
$ mix xref graph --format cycles --label compile-connected
5254
55+
> #### Use the --label option
56+
>
57+
> The job of `mix xref` is to explore relationships between files
58+
> and it is expected that most of your files are either directly
59+
> or indirectly connected. For this reason, it is strongly advised
60+
> to pass the `--label` option to filter the amount of data,
61+
> specifically with `compile-connected` (or `compile`) as values.
62+
5363
Whenever you find a compile-time dependency, such as `lib/a.ex` pointing
5464
to `lib/b.ex`, there are two ways to remove them:
5565
@@ -208,13 +218,11 @@ defmodule Mix.Tasks.Xref do
208218
* `--exclude` - path to exclude. Can be repeated to exclude multiple paths.
209219
210220
* `--label` - only shows relationships with the given label.
211-
The labels are "compile", "export" and "runtime". By default, the `--label`
212-
option does not change how the graph is computed, it simply filters the
213-
printed graph to show only relationships with the given label. However,
214-
you can pass `--only-direct` to trim the graph to only the nodes that
215-
have the direct relationship given by label. There is also a special
216-
label called "compile-connected" that keeps only compile-time files with
217-
at least one transitive dependency. See "Dependency types" section below.
221+
The labels are "compile-connected", "compile", "export" and "runtime".
222+
By default, the `--label` option does not change how the graph is computed,
223+
it simply filters the printed graph to show only relationships with the given
224+
label. However, you can pass `--only-direct` to trim the graph to only the
225+
nodes that have the direct relationship given by label.
218226
219227
* `--group` - provide comma-separated paths to consider as a group. Dependencies
220228
from and into multiple files of the group are considered a single dependency.
@@ -239,6 +247,9 @@ defmodule Mix.Tasks.Xref do
239247
* `--min-cycle-size` - controls the minimum cycle size on formats
240248
like `stats` and `cycles`
241249
250+
* `--min-cycle-label` - controls the minimum number of dependencies
251+
with the given `--label` on a cycle
252+
242253
* `--format` - can be set to one of:
243254
244255
* `pretty` - prints the graph to the terminal using Unicode characters.
@@ -435,6 +446,7 @@ defmodule Mix.Tasks.Xref do
435446
sink: :keep,
436447
source: :keep,
437448
min_cycle_size: :integer,
449+
min_cycle_label: :integer,
438450
output: :string
439451
]
440452

@@ -1218,9 +1230,22 @@ defmodule Mix.Tasks.Xref do
12181230
cycles
12191231
end
12201232

1221-
# :compile_connected is the same
1233+
min_cycle_label =
1234+
if integer = opts[:min_cycle_label] do
1235+
if filter == :all do
1236+
Mix.raise("--min-cycle-label requires the --label option to be given")
1237+
end
1238+
1239+
integer
1240+
else
1241+
1
1242+
end
1243+
1244+
# :compile_connected is the same as :compile
12221245
if cycle_fn = cycle_filter_fn(filter) do
1223-
Enum.filter(cycles, fn {_length, cycle} -> Enum.any?(cycle, cycle_fn) end)
1246+
Enum.filter(cycles, fn {_length, cycle} ->
1247+
Enum.count_until(cycle, cycle_fn, min_cycle_label) == min_cycle_label
1248+
end)
12241249
else
12251250
cycles
12261251
end
@@ -1269,11 +1294,35 @@ defmodule Mix.Tasks.Xref do
12691294
shell.info("#{length(cycles)} cycles found. Showing them in decreasing size:\n")
12701295

12711296
for {length, cycle} <- cycles do
1272-
shell.info("Cycle of length #{length}:\n")
1297+
meta =
1298+
cycle
1299+
|> Enum.reduce({0, 0}, fn
1300+
{_, :compile}, {compile, export} -> {compile + 1, export}
1301+
{_, :export}, {compile, export} -> {compile, export + 1}
1302+
{_, _}, {compile, export} -> {compile, export}
1303+
end)
1304+
|> case do
1305+
{0, 0} ->
1306+
""
1307+
1308+
{compile, export} ->
1309+
info =
1310+
if(compile > 0, do: ["#{compile} compile"], else: []) ++
1311+
if(export > 0, do: ["#{export} export"], else: [])
1312+
1313+
" (" <> Enum.join(info, ", ") <> ")"
1314+
end
1315+
1316+
shell.info("Cycle of length #{length}#{meta}:\n")
12731317

12741318
for {node, type} <- cycle do
1275-
type = if type, do: " (#{type})", else: ""
1276-
shell.info(" " <> node <> type)
1319+
shell.info(
1320+
case type do
1321+
:compile -> [:red, " #{node} (compile)"]
1322+
:export -> " #{node} (export)"
1323+
_ -> " #{node}"
1324+
end
1325+
)
12771326
end
12781327

12791328
shell.info("")

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ defmodule Mix.Tasks.XrefTest do
500500
assert_graph(["--format", "cycles"], """
501501
1 cycles found. Showing them in decreasing size:
502502
503-
Cycle of length 2:
503+
Cycle of length 2 (1 compile):
504504
505505
lib/a.ex (compile)
506506
lib/b.ex
@@ -512,7 +512,7 @@ defmodule Mix.Tasks.XrefTest do
512512
assert_graph(["--format", "cycles", "--label", "compile"], """
513513
1 cycles found. Showing them in decreasing size:
514514
515-
Cycle of length 2:
515+
Cycle of length 2 (1 compile):
516516
517517
lib/a.ex (compile)
518518
lib/b.ex
@@ -524,7 +524,7 @@ defmodule Mix.Tasks.XrefTest do
524524
assert_graph(["--format", "cycles", "--label", "compile-connected"], """
525525
1 cycles found. Showing them in decreasing size:
526526
527-
Cycle of length 2:
527+
Cycle of length 2 (1 compile):
528528
529529
lib/a.ex (compile)
530530
lib/b.ex
@@ -539,7 +539,7 @@ defmodule Mix.Tasks.XrefTest do
539539
assert_graph(["--format", "cycles", "--fail-above", "0"], """
540540
1 cycles found. Showing them in decreasing size:
541541
542-
Cycle of length 2:
542+
Cycle of length 2 (1 compile):
543543
544544
lib/a.ex (compile)
545545
lib/b.ex
@@ -548,18 +548,36 @@ defmodule Mix.Tasks.XrefTest do
548548
end
549549
end
550550

551+
test "cycles with min_cycle_label matching actual length" do
552+
assert_graph(["--format", "cycles", "--label", "compile", "--min-cycle-label", "1"], """
553+
1 cycles found. Showing them in decreasing size:
554+
555+
Cycle of length 2 (1 compile):
556+
557+
lib/a.ex (compile)
558+
lib/b.ex
559+
560+
""")
561+
end
562+
551563
test "cycles with min_cycle_size matching actual length" do
552564
assert_graph(["--format", "cycles", "--min-cycle-size", "2"], """
553565
1 cycles found. Showing them in decreasing size:
554566
555-
Cycle of length 2:
567+
Cycle of length 2 (1 compile):
556568
557569
lib/a.ex (compile)
558570
lib/b.ex
559571
560572
""")
561573
end
562574

575+
test "cycles with min_cycle_label greater than actual length" do
576+
assert_graph(["--format", "cycles", "--label", "compile", "--min-cycle-label", "2"], """
577+
No cycles found
578+
""")
579+
end
580+
563581
test "cycles with min_cycle_size greater than actual length" do
564582
assert_graph(["--format", "cycles", "--min-cycle-size", "3"], """
565583
No cycles found

0 commit comments

Comments
 (0)