mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Updated paths in test files to reflect new directory structure for RTL and script files. - Changed references from rtl/mini to rtl/ip and scripts/rtl where applicable. - Added a new test file for Tiny product isolation, ensuring independent ABI and bus acceptance. - Enhanced tests for memory map generation and protocol contracts for Tiny architecture. - Ensured that all tests are aligned with the latest directory structure and naming conventions.
146 lines
5.2 KiB
Python
146 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Check the self-owned embedded C surface without scanning vendored code."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
SOURCE_SUFFIXES = {".c", ".h"}
|
|
TINY_TOKEN = re.compile(r"\btiny[a-zA-Z0-9_]*\b", re.IGNORECASE)
|
|
LEGACY_INCLUDE = re.compile(r"#\s*include\s*[<\"](?:rs_|tiny)", re.IGNORECASE)
|
|
|
|
|
|
def load_policy(root: Path) -> dict[str, object]:
|
|
path = root / "quality" / "embedded_c_policy.json"
|
|
with path.open(encoding="utf-8") as policy_file:
|
|
return json.load(policy_file)
|
|
|
|
|
|
def is_excluded(relative_path: str, prefixes: list[str]) -> bool:
|
|
return any(relative_path.startswith(prefix) for prefix in prefixes)
|
|
|
|
|
|
def embedded_c_files(root: Path, policy: dict[str, object]) -> list[Path]:
|
|
prefixes = [str(prefix) for prefix in policy["excluded_prefixes"]]
|
|
files: list[Path] = []
|
|
for source_root in (root / "crt", root / "app"):
|
|
if not source_root.exists():
|
|
continue
|
|
for path in source_root.rglob("*"):
|
|
if not path.is_file() or path.suffix not in SOURCE_SUFFIXES:
|
|
continue
|
|
relative_path = path.relative_to(root).as_posix()
|
|
if not is_excluded(relative_path, prefixes):
|
|
files.append(path)
|
|
return sorted(files)
|
|
|
|
|
|
def format_issues(path: Path) -> list[str]:
|
|
data = path.read_bytes()
|
|
issues: list[str] = []
|
|
if b"\r" in data:
|
|
issues.append("contains CR line endings")
|
|
if data and not data.endswith(b"\n"):
|
|
issues.append("does not end with a newline")
|
|
|
|
for line_number, raw_line in enumerate(data.splitlines(), start=1):
|
|
if raw_line.rstrip(b" \t") != raw_line:
|
|
issues.append(f"line {line_number}: trailing whitespace")
|
|
if b"\t" in raw_line:
|
|
issues.append(f"line {line_number}: tab indentation is not allowed")
|
|
return issues
|
|
|
|
|
|
def policy_issues(root: Path, path: Path, policy: dict[str, object]) -> list[str]:
|
|
relative_path = path.relative_to(root).as_posix()
|
|
compatibility_files = {str(item) for item in policy["compatibility_files"]}
|
|
forbidden_calls = "|".join(str(item) for item in policy["forbidden_calls"])
|
|
forbidden_call = re.compile(rf"\b(?:{forbidden_calls})\s*\(")
|
|
text = path.read_text(encoding="utf-8")
|
|
issues: list[str] = []
|
|
|
|
# Product names in comments/string literals are prose, not retired SDK
|
|
# identifiers. Legacy include paths are still checked against the raw text.
|
|
code = re.sub(r'/\*.*?\*/|//[^\n]*|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'',
|
|
" ", text, flags=re.DOTALL)
|
|
if TINY_TOKEN.search(code):
|
|
issues.append("contains a retired tiny-prefixed identifier or label")
|
|
if LEGACY_INCLUDE.search(text):
|
|
issues.append("contains a retired legacy include path")
|
|
if relative_path not in compatibility_files and forbidden_call.search(text):
|
|
issues.append("uses a banned unsafe C library call")
|
|
return issues
|
|
|
|
|
|
def print_issues(root: Path, files: list[Path], callback: object) -> int:
|
|
count = 0
|
|
for path in files:
|
|
for issue in callback(path):
|
|
print(f"{path.relative_to(root)}: {issue}")
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def run_clang_format(files: list[Path], formatter: str) -> int:
|
|
executable = shutil.which(formatter)
|
|
if executable is None:
|
|
print(f"clang-format executable not found: {formatter}", file=sys.stderr)
|
|
return 1
|
|
subprocess.run([executable, "-i", *(str(path) for path in files)], check=True)
|
|
return 0
|
|
|
|
|
|
def check_clang_format(files: list[Path], formatter: str) -> int:
|
|
executable = shutil.which(formatter)
|
|
if executable is None:
|
|
print(f"clang-format executable not found: {formatter}", file=sys.stderr)
|
|
return 1
|
|
result = subprocess.run(
|
|
[executable, "--dry-run", "--Werror", *(str(path) for path in files)],
|
|
check=False,
|
|
)
|
|
return result.returncode
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
parser.add_argument("--format-check", action="store_true")
|
|
parser.add_argument("--clang-format-check", action="store_true")
|
|
parser.add_argument("--policy-check", action="store_true")
|
|
parser.add_argument("--apply-format", action="store_true")
|
|
parser.add_argument("--clang-format", default="clang-format")
|
|
args = parser.parse_args()
|
|
|
|
if not (args.format_check or args.clang_format_check or args.policy_check or args.apply_format):
|
|
parser.error("select a format, policy, or apply action")
|
|
|
|
root = args.root.resolve()
|
|
policy = load_policy(root)
|
|
files = embedded_c_files(root, policy)
|
|
failures = 0
|
|
if args.format_check:
|
|
failures += print_issues(root, files, format_issues)
|
|
if args.policy_check:
|
|
failures += print_issues(
|
|
root,
|
|
files,
|
|
lambda path: policy_issues(root, path, policy),
|
|
)
|
|
if args.clang_format_check:
|
|
failures += check_clang_format(files, args.clang_format)
|
|
if args.apply_format:
|
|
return run_clang_format(files, args.clang_format)
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|