-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathassert_dep_direction.py
More file actions
executable file
·99 lines (77 loc) · 2.99 KB
/
Copy pathassert_dep_direction.py
File metadata and controls
executable file
·99 lines (77 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""Assert the kernel's dependency direction is correct.
WO-9 invariant: the kernel may depend on actenon-protocol and nothing
else in the actenon ecosystem. Cloud, Permit, and Scan may depend on the
kernel; the kernel never depends on them.
This script reads [project].dependencies from pyproject.toml, extracts
requirements whose name starts with "actenon", and fails if the set
differs from {"actenon-protocol"}.
Optional extras and dev groups are exempt — a [cloud] extra that pulls
in a cloud SDK is fine; a runtime dependency on actenon-cloud is not.
Usage:
python scripts/assert_dep_direction.py
python scripts/assert_dep_direction.py --check # same (alias for CI)
Exit codes:
0 — dependency direction is correct
1 — a forbidden actenon-* dependency was found
"""
from __future__ import annotations
import argparse
import sys
import tomllib
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
PYPROJECT = REPO_ROOT / "pyproject.toml"
ALLOWED_ACTENON_DEPS = frozenset({"actenon-protocol"})
def extract_actenon_deps(deps: list[str]) -> set[str]:
"""Extract actenon-* package names from a dependency list.
Handles:
- "actenon-protocol>=1.1.0,<2" -> "actenon-protocol"
- "actenon-kernel[asymmetric]>=0.1.0" -> "actenon-kernel"
- "actenon-protocol @ git+https://..." -> "actenon-protocol"
"""
result: set[str] = set()
for dep in deps:
# Strip version constraints, extras, URLs
# Take everything before the first [ (extras), <, >, =, !, ~, @, or space
name = dep.strip()
for i, ch in enumerate(name):
if ch in "[<>=!~@ ":
name = name[:i]
break
name = name.strip()
if name.startswith("actenon"):
result.add(name)
return result
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true", help="alias for CI")
args = parser.parse_args()
if not PYPROJECT.exists():
print(f"ERROR: {PYPROJECT} not found", file=sys.stderr)
return 2
with open(PYPROJECT, "rb") as f:
data = tomllib.load(f)
runtime_deps = data.get("project", {}).get("dependencies", [])
actenon_deps = extract_actenon_deps(runtime_deps)
forbidden = actenon_deps - ALLOWED_ACTENON_DEPS
if forbidden:
print(
f"FAIL: kernel has forbidden actenon-* runtime dependencies: "
f"{sorted(forbidden)}",
file=sys.stderr,
)
print(
f"Allowed: {sorted(ALLOWED_ACTENON_DEPS)}",
file=sys.stderr,
)
print(
"The kernel may depend on actenon-protocol only. Cloud, Permit, "
"and Scan may depend on the kernel; the kernel never depends on them.",
file=sys.stderr,
)
return 1
print(f"OK: kernel runtime actenon deps = {sorted(actenon_deps)}")
return 0
if __name__ == "__main__":
sys.exit(main())