Files

183 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""Read-only, bounded comparison of a CV181X EmxRes project and an explicit SDK tree."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
import bsp_preflight
import project_guard
CONFIG_RE = re.compile(r"^(CONFIG_[A-Za-z0-9_]+)=(.+)$", re.MULTILINE)
CONFIG_PREFIXES = (
"CONFIG_CHIP_", "CONFIG_BOARD_", "CONFIG_DDR_CFG_", "CONFIG_TOOLCHAIN_",
"CONFIG_STORAGE_TYPE_", "CONFIG_NANDFLASH_",
)
CONFIG_KEYS = {"CONFIG_ARCH", "CONFIG_SDK_VER", "CONFIG_ENABLE_FREERTOS", "CONFIG_BOOT_IMAGE_SINGLE_DTB"}
def selected_config(path: Path) -> dict[str, str]:
if not path.is_file() or path.is_symlink():
return {}
return {
key: value for key, value in CONFIG_RE.findall(path.read_text(encoding="utf-8"))
if key in CONFIG_KEYS or key.startswith(CONFIG_PREFIXES)
}
def sync_scripts_match(project: Path) -> bool:
kernel = project / "configs/kernel/ep_kernel.sh"
uboot = project / "configs/uboot/ep_uboot.sh"
if any(not path.is_file() or path.is_symlink() for path in (kernel, uboot)):
return False
kernel_lines = {line.strip() for line in kernel.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")}
uboot_lines = {line.strip() for line in uboot.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")}
return all(token in kernel_lines for token in (
"EP_KERNEL_BUILD_TOP=build/boards/cv181x/$CV_BUILD_TYPE",
"EP_KERNEL_BASE_DTSI=build/boards/default/dts/cv181x",
"cp $EP_KERNEL_CONFIGS_PATH/memmap.py $EP_KERNEL_BUILD_TOP/memmap.py",
"cp $EP_KERNEL_CONFIGS_PATH/cvitek_$CV_BUILD_TYPE\"_defconfig\" $EP_KERNEL_BUILD_TOP/linux/cvitek_$CV_BUILD_TYPE\"_defconfig\"",
"cp $EP_KERNEL_CONFIGS_PATH/*.dts $EP_KERNEL_BUILD_TOP/dts_arm/",
"cp $EP_KERNEL_CONFIGS_PATH/*.dtsi $EP_KERNEL_BASE_DTSI/",
)) and all(token in uboot_lines for token in (
"EP_UBOOT_BUILD_TOP=build/boards/cv181x/$CV_BUILD_TYPE",
"cp $EP_UBOOT_CONFIGS_PATH/$CV_BUILD_TYPE\"_defconfig\" $EP_UBOOT_BUILD_TOP/",
"cp $EP_UBOOT_CONFIGS_PATH/cvitek_$CV_BUILD_TYPE\"_defconfig\" $EP_UBOOT_BUILD_TOP/u-boot/cvitek_$CV_BUILD_TYPE\"_defconfig\"",
"cp $EP_UBOOT_CONFIGS_PATH/partition_spinand.xml $EP_UBOOT_BUILD_TOP/partition/partition_spinand.xml",
"cp $EP_UBOOT_CONFIGS_PATH/cvi_board_init.c $EP_UBOOT_BUILD_TOP/u-boot/cvi_board_init.c",
))
def mapped_files(build_type: str, project: Path) -> list[tuple[Path, Path]]:
board = Path("build/boards/cv181x") / build_type
files = [
(Path("configs/uboot") / f"{build_type}_defconfig", board / f"{build_type}_defconfig"),
(Path("configs/uboot") / f"cvitek_{build_type}_defconfig", board / "u-boot" / f"cvitek_{build_type}_defconfig"),
(Path("configs/uboot/cvi_board_init.c"), board / "u-boot/cvi_board_init.c"),
(Path("configs/uboot/partition_spinand.xml"), board / "partition/partition_spinand.xml"),
(Path("configs/kernel/memmap.py"), board / "memmap.py"),
(Path("configs/kernel") / f"cvitek_{build_type}_defconfig", board / "linux" / f"cvitek_{build_type}_defconfig"),
]
for path in sorted((project / "configs/kernel").glob("*.dts")):
files.append((path.relative_to(project), board / "dts_arm" / path.name))
for path in sorted((project / "configs/kernel").glob("*.dtsi")):
files.append((path.relative_to(project), Path("build/boards/default/dts/cv181x") / path.name))
return files
def inspect(project: Path, sdk_root: Path) -> dict[str, object]:
if project.is_symlink() or not project.is_dir() or sdk_root.is_symlink() or not sdk_root.is_dir():
raise ValueError("项目与 SDK 必须是存在的普通目录,不能是软链接")
root = project_guard.normalize_root(project.parent)
project = project_guard.direct_child(root, project.name, "project", True)
sdk_root = sdk_root.resolve()
env_path = project / "buildEnvConfig.sh"
if not env_path.is_file() or env_path.is_symlink():
raise ValueError("项目缺少普通 buildEnvConfig.sh")
env = bsp_preflight.parse_assignments(env_path)
if not env.get("EP_CHIP", "").startswith("CV181"):
raise ValueError("本检查器仅适用于 EP_CHIP=CV181* 项目")
if env.get("EP_COMPANY") != "Cvitek" or env.get("EP_FLASH_TYPE") != "nand":
raise ValueError("本检查器目前只核对 Cvitek CV181X SPI NAND 构建链")
if not env.get("EP_SDK_VER") or sdk_root.name != env["EP_SDK_VER"]:
raise ValueError("指定 SDK 目录名与项目 EP_SDK_VER 不符")
layout = bsp_preflight.resolve_board_layout(project, env.get("EP_CHIP"))
if layout["error"] or layout["layout"] != "split-kernel-uboot":
raise ValueError(str(layout["error"] or "需要 kernel/uboot 分目录布局"))
if not sync_scripts_match(project):
raise ValueError("同步脚本与已知 CV181X 复制契约不符;不猜测 SDK 写入目标")
build_type = str(layout["board_build_type"])
alerts: list[dict[str, str]] = []
if not build_type.startswith(env["EP_CHIP"].lower() + "_") or "spinand" not in build_type:
alerts.append({"code": "board_identity", "message": "板级配置名与芯片或 NAND 类型不一致,需人工核对"})
mappings: list[dict[str, object]] = []
for source_rel, dest_rel in mapped_files(build_type, project):
source = project / source_rel
dest = sdk_root / dest_rel
resolved = dest.resolve(strict=False)
entry: dict[str, object] = {
"project_file": str(source),
"sdk_path": str(dest),
"sdk_real_path": str(resolved),
"sdk_is_symlink": dest.is_symlink(),
"project_exists": source.is_file() and not source.is_symlink(),
"sdk_exists": dest.is_file(),
"same_content": None,
}
if not resolved.is_relative_to(sdk_root):
entry["sdk_exists"] = False
alerts.append({"code": "sdk_link_escape", "message": f"SDK 目标软链接越出指定 SDK: {dest_rel}"})
elif source.is_file() and not source.is_symlink() and dest.is_file():
entry["same_content"] = project_guard.sha256_file(source) == project_guard.sha256_file(dest)
if entry["sdk_is_symlink"]:
alerts.append({"code": "shared_sdk_target", "message": f"复制可能改写公共目标: {resolved}"})
if entry["same_content"] is False:
alerts.append({"code": "sdk_copy_differs", "message": f"项目与 SDK 当前内容不同: {source_rel}"})
else:
alerts.append({"code": "mapping_missing", "message": f"项目或 SDK 文件缺失: {source_rel}"})
if entry["sdk_is_symlink"] and entry["same_content"] is None and resolved.is_relative_to(sdk_root):
alerts.append({"code": "shared_sdk_target", "message": f"复制可能改写公共目标: {resolved}"})
mappings.append(entry)
source_config = project / "configs/uboot" / f"{build_type}_defconfig"
expected = selected_config(source_config)
actual = selected_config(sdk_root / "build/.config")
differences = [
{"key": key, "project": value, "sdk_generated": actual.get(key)}
for key, value in sorted(expected.items()) if actual.get(key) != value
] if actual else []
if not actual:
alerts.append({"code": "generated_config_missing", "message": "SDK build/.config 不存在,无法核对生成配置"})
elif differences:
alerts.append({"code": "generated_config_differs", "message": "生成配置与项目板级配置存在差异;可能属于其他项目或未重新生成"})
tool = root / "tools/BuildCvitekSDKTool.sh"
order = "unknown"
if tool.is_file() and not tool.is_symlink():
content = tool.read_text(encoding="utf-8")
positions = [content.find(s) for s in ("defconfig $CV_BUILD_TYPE", "Ep_Uboot_Config_Func", "Ep_Kernel_Config_Func")]
if -1 not in positions:
order = "sync-after-defconfig" if positions[0] < positions[1] and positions[0] < positions[2] else "other"
if order == "sync-after-defconfig":
alerts.append({"code": "sync_after_defconfig", "message": "工具先生成 .config 再同步项目配置;顶层配置变更可能不在当次生效"})
return {
"schema_version": 1,
"kind": "cv181x-sdk-readonly-inspection",
"project": str(project),
"sdk_root": str(sdk_root),
"chip": env["EP_CHIP"],
"sdk_version": env.get("EP_SDK_VER"),
"board_build_type": build_type,
"build_order": order,
"mappings": mappings,
"generated_config_differences": differences,
"alerts": alerts,
"note": "只读磁盘快照;未执行同步、编译或板上验证。映射以已识别的复制脚本为前提。",
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project", required=True, type=Path)
parser.add_argument("--sdk-root", required=True, type=Path)
args = parser.parse_args()
try:
result = inspect(args.project, args.sdk_root)
except (ValueError, OSError, UnicodeDecodeError, project_guard.GuardError) as exc:
print(json.dumps({"passed": False, "error": str(exc)}, ensure_ascii=False, indent=2))
return 2
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())