import re
import sys
import tomllib
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
EBUILD_DIR = REPO_ROOT / "packaging" / "gentoo" / "dev-util" / "codediff"
CRATES_BLOCK = re.compile(r'^CRATES="\n(?:\t[^\n]*\n)*"$', re.MULTILINE)
def crates_from_lockfile(lockfile: Path) -> list[str]:
data = tomllib.loads(lockfile.read_text())
crates = []
for package in data["package"]:
source = package.get("source")
if source is None:
if package["name"] != "codediff":
raise SystemExit(
f"error: {package['name']} {package['version']} has no source - a path or git "
f"dependency needs explicit handling in the ebuild, not a silent drop"
)
continue
if not source.startswith("registry+"):
raise SystemExit(
f"error: {package['name']} comes from {source!r}, not a registry - cargo.eclass's "
f"CRATES mechanism only handles crates.io"
)
crates.append(f"{package['name']}@{package['version']}")
return sorted(crates)
REQUIRED_AFTER_SUBSTITUTION = (
"inherit ",
"DESCRIPTION=",
"HOMEPAGE=",
"SRC_URI=",
"LICENSE=",
"src_install()",
)
def _assert_intact(ebuild: Path, updated: str) -> None:
missing = [token for token in REQUIRED_AFTER_SUBSTITUTION if token not in updated]
if missing:
raise SystemExit(
f"error: substituting CRATES into {ebuild.name} removed {', '.join(missing)} - "
f"refusing to write. The CRATES regex has overrun its block; fix it before rerunning."
)
def main() -> int:
check_only = "--check" in sys.argv[1:]
crates = crates_from_lockfile(REPO_ROOT / "Cargo.lock")
block = 'CRATES="\n' + "\n".join(f"\t{crate}" for crate in crates) + '\n"'
ebuilds = sorted(EBUILD_DIR.glob("*.ebuild"))
if not ebuilds:
raise SystemExit(f"error: no ebuild found under {EBUILD_DIR}")
stale = []
for ebuild in ebuilds:
text = ebuild.read_text()
if not CRATES_BLOCK.search(text):
raise SystemExit(f'error: {ebuild} has no CRATES="..." block to replace')
updated = CRATES_BLOCK.sub(lambda _: block, text, count=1)
_assert_intact(ebuild, updated)
if updated == text:
continue
if check_only:
stale.append(ebuild)
else:
ebuild.write_text(updated)
print(f"updated {ebuild.relative_to(REPO_ROOT)} ({len(crates)} crates)")
if stale:
for ebuild in stale:
print(
f"error: {ebuild.relative_to(REPO_ROOT)} is out of date with Cargo.lock",
file=sys.stderr,
)
print("run: python3 scripts/generate_gentoo_crates.py", file=sys.stderr)
return 1
if check_only:
print(f"CRATES up to date ({len(crates)} crates)")
return 0
if __name__ == "__main__":
sys.exit(main())