57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Enforce crate dependency boundaries for the notfiles workspace:
|
||
|
|
- notcore must remain a leaf crate (no deps on other notfiles crates)
|
||
|
|
- notfiles, notsecrets, nothooks may depend on notcore but NOT on each other
|
||
|
|
- notstrap may depend on notcore + feature crates (it's the orchestrator)
|
||
|
|
|
||
|
|
Exits 0 if boundaries are clean, 1 if violations found.
|
||
|
|
"""
|
||
|
|
import json
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
|
||
|
|
LEAF = "notcore"
|
||
|
|
FEATURE_CRATES = {"notfiles", "notsecrets", "nothooks"}
|
||
|
|
ORCHESTRATOR = "notstrap"
|
||
|
|
WORKSPACE_CRATES = FEATURE_CRATES | {LEAF, ORCHESTRATOR}
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
["cargo", "metadata", "--no-deps", "--format-version", "1"],
|
||
|
|
capture_output=True, text=True, check=True,
|
||
|
|
)
|
||
|
|
meta = json.loads(result.stdout)
|
||
|
|
|
||
|
|
# Build name -> deps map for workspace members only
|
||
|
|
crate_deps: dict[str, set[str]] = {}
|
||
|
|
for pkg in meta["packages"]:
|
||
|
|
name = pkg["name"]
|
||
|
|
if name not in WORKSPACE_CRATES:
|
||
|
|
continue
|
||
|
|
deps = {d["name"] for d in pkg["dependencies"] if d["name"] in WORKSPACE_CRATES}
|
||
|
|
crate_deps[name] = deps
|
||
|
|
|
||
|
|
violations = []
|
||
|
|
|
||
|
|
# notcore must have no workspace deps
|
||
|
|
if crate_deps.get(LEAF):
|
||
|
|
violations.append(f"{LEAF} depends on workspace crates: {crate_deps[LEAF]}")
|
||
|
|
|
||
|
|
# feature crates must not depend on each other
|
||
|
|
for crate in FEATURE_CRATES:
|
||
|
|
forbidden = crate_deps.get(crate, set()) & FEATURE_CRATES
|
||
|
|
if forbidden:
|
||
|
|
violations.append(f"{crate} depends on sibling feature crates: {forbidden}")
|
||
|
|
|
||
|
|
if violations:
|
||
|
|
print("FAIL: crate dependency boundary violations detected:")
|
||
|
|
for v in violations:
|
||
|
|
print(f" - {v}")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
print("OK: crate dependency boundaries are clean")
|
||
|
|
print(f" {LEAF}: no workspace deps (leaf)")
|
||
|
|
for c in sorted(FEATURE_CRATES):
|
||
|
|
print(f" {c}: depends only on {LEAF}")
|
||
|
|
print(f" {ORCHESTRATOR}: orchestrator (unrestricted)")
|