Files
retroSoC/scripts/manifest.py
T
maksyuki 57c9d334ea feat: refactor MPW preparation and add regression and flow scripts
- Updated  to utilize a new dependency locking mechanism and improved repository management.
- Introduced  for running regression tests with configurable command sets for different profiles.
- Added  to execute commands with structured logging and result reporting.
- Enhanced  to support fetching specific revisions from git repositories.
- Modified  to use the new dependency source structure.
- Updated OpenSTA makefile and TCL script to include metrics reporting.
- Improved synthesis scripts to utilize the new flow execution model.
- Added tests for dependency locking, flow execution, and warning analysis.
- Ensured that all scripts and tools are consistent with the new structure and functionality.
2026-07-10 17:48:11 +08:00

124 lines
3.9 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import platform
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.dependency_lock import DEFAULT_LOCK, lock_digest, load_lock # noqa: E402
from scripts.setup_helpers import atomic_write # noqa: E402
TOOLS = {
"python": ("python3", "--version"),
"make": ("make", "--version"),
"riscv_gcc": ("riscv32-unknown-elf-gcc", "--version"),
"iverilog": ("iverilog", "-V"),
"verilator": ("verilator", "--version"),
"yosys": ("yosys", "--version"),
"opensta": ("sta", "-version"),
}
def command_output(command: tuple[str, ...]) -> str | None:
if shutil.which(command[0]) is None:
return None
result = subprocess.run(command, text=True, capture_output=True, check=False)
output = (result.stdout or result.stderr).strip().splitlines()
return output[0] if output else None
def git_info(root: Path) -> dict[str, object]:
def git(*args: str) -> str:
return subprocess.check_output(
["git", "-C", str(root), *args], text=True, stderr=subprocess.DEVNULL
).strip()
try:
return {
"commit": git("rev-parse", "HEAD"),
"branch": git("rev-parse", "--abbrev-ref", "HEAD"),
"dirty": bool(git("status", "--porcelain")),
}
except (subprocess.CalledProcessError, FileNotFoundError):
return {"commit": "unknown", "branch": "unknown", "dirty": None}
def parse_pairs(values: list[str]) -> dict[str, str]:
parsed: dict[str, str] = {}
for item in values:
key, separator, value = item.partition("=")
if not separator or not key or key in parsed:
raise ValueError(f"invalid or duplicate key=value item: {item}")
parsed[key] = value
return dict(sorted(parsed.items()))
def file_hashes(root: Path) -> dict[str, str]:
hashes: dict[str, str] = {}
if not root.exists():
return hashes
for path in sorted(root.rglob("*.fl")):
hashes[str(path.relative_to(root))] = hashlib.sha256(path.read_bytes()).hexdigest()
return hashes
def create_manifest(args: argparse.Namespace) -> dict[str, object]:
root = args.root.resolve()
lock = args.lock.resolve()
load_lock(lock)
variant_root = args.output.resolve().parents[1]
return {
"schema_version": 1,
"generated_at": datetime.now(timezone.utc).isoformat(),
"repository": git_info(root),
"profile": args.profile,
"configuration": parse_pairs(args.config),
"dependency_lock": {
"path": str(lock.relative_to(root)),
"sha256": lock_digest(lock),
},
"host": {
"platform": platform.platform(),
"machine": platform.machine(),
},
"tools": {name: command_output(command) for name, command in TOOLS.items()},
"filelists": file_hashes(variant_root),
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Create a retroSoC build manifest")
subparsers = parser.add_subparsers(dest="command", required=True)
create = subparsers.add_parser("create")
create.add_argument("--root", type=Path, required=True)
create.add_argument("--lock", type=Path, default=DEFAULT_LOCK)
create.add_argument("--output", type=Path, required=True)
create.add_argument("--profile", required=True)
create.add_argument("--config", action="append", default=[])
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
data = create_manifest(args)
except ValueError as error:
raise SystemExit(str(error)) from error
atomic_write(args.output, json.dumps(data, indent=2, sort_keys=True) + "\n")
print(f"manifest: {args.output.resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())