Skip to content

Commit 3483dd2

Browse files
committed
Find cycles in recipes
1 parent 12f8014 commit 3483dd2

File tree

2 files changed

+32
-0
lines changed

2 files changed

+32
-0
lines changed

alibuild_helpers/utilities.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,24 @@ def topological_sort(specs):
6565
edges = [(pkg, dep) for pkg, dep in edges if dep != current_package]
6666
# ...but keep blocking those that still depend on other stuff!
6767
leaves.extend(new_leaves - {pkg for pkg, _ in edges})
68+
# If we have any edges left, we have a cycle
69+
if edges:
70+
# Find a cycle by following dependencies
71+
cycle = []
72+
start = edges[0][0] # Start with any remaining package
73+
current = start
74+
while True:
75+
cycle.append(current)
76+
# Find what current depends on
77+
for pkg, dep in edges:
78+
if pkg == current:
79+
current = dep
80+
break
81+
if current in cycle: # We found a cycle
82+
cycle = cycle[cycle.index(current):] # Trim to just the cycle
83+
dieOnError(True, "Dependency cycle detected: " + " -> ".join(cycle + [cycle[0]]))
84+
if current == start: # We've gone full circle
85+
break
6886

6987

7088
def resolve_store_path(architecture, spec_hash):

tests/test_utilities.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from alibuild_helpers.utilities import resolve_version
1313
from alibuild_helpers.utilities import topological_sort
1414
from alibuild_helpers.utilities import resolveFilename, resolveDefaultsFilename
15+
import alibuild_helpers
1516
import os
1617
import string
1718

@@ -344,5 +345,18 @@ def test_dont_drop_packages(self) -> None:
344345
self.assertEqual(frozenset(specs.keys()),
345346
frozenset(topological_sort(specs)))
346347

348+
def test_cycle(self) -> None:
349+
"""Test that dependency cycles are detected and reported."""
350+
specs = {
351+
"A": {"package": "A", "requires": ["B"]},
352+
"B": {"package": "B", "requires": ["C"]},
353+
"C": {"package": "C", "requires": ["D"]},
354+
"D": {"package": "D", "requires": ["A"]}
355+
}
356+
with patch.object(alibuild_helpers.log, 'error') as mock_error:
357+
with self.assertRaises(SystemExit) as cm:
358+
list(topological_sort(specs))
359+
self.assertEqual(cm.exception.code, 1)
360+
mock_error.assert_called_once_with("%s", "Dependency cycle detected: A -> B -> C -> D -> A")
347361
if __name__ == '__main__':
348362
unittest.main()

0 commit comments

Comments
 (0)