Files
firmware-lowlevel-development/skills/firmware-lowlevel-bsp-development/scripts/bsp/inspect_cv184x_sdk.py
T

211 lines
11 KiB
Python

#!/usr/bin/env python3
"""Read-only CV184X project-to-SDK snapshot review; never run sync or build."""
from __future__ import annotations
import argparse
import json
import re
import subprocess
from pathlib import Path
import bsp_preflight
import project_guard
CONFIG_RE = re.compile(r"^(CONFIG_[A-Za-z0-9_]+)=(.+)$", re.MULTILINE)
NOT_SET_RE = re.compile(r"^# (CONFIG_[A-Za-z0-9_]+) is not set$", re.MULTILINE)
TRACKED_KEYS = {"CONFIG_DUAL_OS", "CONFIG_ION_SIZE", "CONFIG_RTOS_ION_SIZE", "CONFIG_RTOS_SYS_SIZE",
"CONFIG_DRAM_SIZE", "CONFIG_ENABLE_RTT", "CONFIG_RTOS_BUILD_IN_FIP",
"CONFIG_ROOTFS_B", "CONFIG_ROOTFS_RECOVERY", "CONFIG_VENDOR_PARTITION", "CONFIG_SKIP_UBOOT"}
TRACKED_PREFIXES = ("CONFIG_CHIP_", "CONFIG_BOARD_", "CONFIG_STORAGE_TYPE_", "CONFIG_SENSOR_")
CHIP_LINE = re.compile(r'\$EP_CHIP\s*==\s*"(CV184[^"]+)"')
FLASH_LINE = re.compile(r'\$EP_FLASH_TYPE\s*==\s*"(nor|nand|emmc)"')
BOARD_LINE = re.compile(r'^\s*CV_BUILD_TYPE=([a-z0-9_]+)\s*$')
def selected_config(path: Path) -> dict[str, str]:
if not path.is_file() or path.is_symlink():
return {}
content = path.read_text(encoding="utf-8")
values = {key: "n" for key in NOT_SET_RE.findall(content)
if key in TRACKED_KEYS or key.startswith(TRACKED_PREFIXES)}
values.update({key: value for key, value in CONFIG_RE.findall(content)
if key in TRACKED_KEYS or key.startswith(TRACKED_PREFIXES)})
return values
def partition_name(config: dict[str, str], flash: str) -> str:
if config.get("CONFIG_SKIP_UBOOT") == "y":
variant = "fastboot_"
elif config.get("CONFIG_VENDOR_PARTITION") == "y":
variant = "v_"
elif config.get("CONFIG_ROOTFS_B") == "y" and config.get("CONFIG_ROOTFS_RECOVERY") == "y":
variant = "abr_"
elif config.get("CONFIG_ROOTFS_B") == "y":
variant = "ab_"
elif config.get("CONFIG_ROOTFS_RECOVERY") == "y":
variant = "ar_"
else:
variant = ""
return f"partition_{variant}{'spinand' if flash == 'nand' else 'spinor' if flash == 'nor' else flash}.xml"
def build_type_from_tool(tool: Path, chip: str, flash: str) -> tuple[str | None, str]:
if not tool.is_file() or tool.is_symlink():
return None, "unknown"
content = tool.read_text(encoding="utf-8")
found_chip: str | None = None
found_flash: str | None = None
build_type: str | None = None
for line in content.splitlines():
chip_match = CHIP_LINE.search(line)
if chip_match:
found_chip, found_flash = chip_match.group(1), None
flash_match = FLASH_LINE.search(line)
if flash_match:
found_flash = flash_match.group(1)
board_match = BOARD_LINE.match(line)
if board_match and found_chip == chip and found_flash == flash:
build_type = board_match.group(1)
if "export CV_BUILD_TYPE" in line:
break
sync_match = re.search(r"(?m)^[ \t]*Ep_Sync_SDK_Config_Func[ \t]*$", content)
defconfig_match = re.search(r"(?m)^[ \t]*defconfig[ \t]+\$CV_BUILD_TYPE[ \t]*$", content)
order = "unknown"
if sync_match and defconfig_match:
order = "sync-before-defconfig" if sync_match.start() < defconfig_match.start() else "sync-after-defconfig"
return build_type, order
def tracked_git_state(sdk_root: Path) -> list[dict[str, object]]:
result = []
for name in ("build", "cvitek_cv184x_patch", "linux_5.10", "u-boot-2021.10", "fsbl"):
path = sdk_root / name
if not (path / ".git").exists():
result.append({"repo": name, "status": "not-a-repository"})
continue
proc = subprocess.run(("git", "-C", str(path), "status", "--porcelain=v1", "--untracked-files=no"),
capture_output=True, text=True, check=False)
lines = [line for line in proc.stdout.splitlines() if line]
result.append({"repo": name, "status": "dirty" if lines else "clean" if proc.returncode == 0 else "unknown",
"tracked_change_count": len(lines), "sample": lines[:8]})
return result
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)
chip, flash = env.get("EP_CHIP", ""), env.get("EP_FLASH_TYPE", "")
if not chip.startswith("CV184") or env.get("EP_COMPANY") != "Cvitek":
raise ValueError("本检查器只适用于 Cvitek CV184X 项目")
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, chip)
if layout["error"] or layout["layout"] != "board-directory":
raise ValueError(str(layout["error"] or "CV184X 需要单板卡配置目录"))
board = str(layout["board_build_type"])
sync = project / "configs/EPSYNCSDKConfig.sh"
if not sync.is_file() or sync.is_symlink():
raise ValueError("缺少普通 EPSYNCSDKConfig.sh")
active = {line.split("#", 1)[0].strip() for line in sync.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")}
if not {"EP_UBOOT_BUILD_TOP=build/boards/cv184x/$CV_BUILD_TYPE",
f"rsync -av $EP_CONFIGS_PATH/{board}/. $EP_UBOOT_BUILD_TOP/"} <= active:
raise ValueError("同步脚本不符合已知 CV184X 映射;不猜测 SDK 目标")
expected, order = build_type_from_tool(root / "tools/BuildCvitek184XSDKTool.sh", chip, flash)
alerts: list[dict[str, str]] = []
if expected != board:
alerts.append({"code": "build_type_mismatch", "message": f"统一脚本板型 {expected!r} 与项目目录 {board!r} 不一致"})
if order != "sync-before-defconfig":
alerts.append({"code": "build_order_unknown", "message": "无法确认先同步项目配置再生成 SDK .config"})
board_dir = project / "configs" / board
sdk_board = sdk_root / "build/boards/cv184x" / board
mappings: list[dict[str, object]] = []
for source in sorted(board_dir.rglob("*")):
if source.is_dir() and not source.is_symlink():
continue
rel = source.relative_to(board_dir)
dest = sdk_board / rel
resolved = dest.resolve(strict=False)
inside = resolved.is_relative_to(sdk_root)
entry: dict[str, object] = {"project_file": str(source), "sdk_path": str(dest),
"sdk_real_path": str(resolved), "sdk_is_symlink": dest.is_symlink(),
"same_content": None}
if source.is_symlink() or not source.is_file():
alerts.append({"code": "project_file_not_regular", "message": str(source)})
elif not inside:
alerts.append({"code": "sdk_link_escape", "message": str(dest)})
elif not dest.is_file():
alerts.append({"code": "mapping_missing", "message": str(dest)})
else:
entry["same_content"] = project_guard.sha256_file(source) == project_guard.sha256_file(dest)
if entry["same_content"] is False:
alerts.append({"code": "sdk_copy_differs", "message": str(rel)})
if dest.is_symlink() and inside:
alerts.append({"code": "shared_sdk_target", "message": str(resolved)})
mappings.append(entry)
source_cfg = selected_config(board_dir / f"{board}_defconfig")
generated_cfg = selected_config(sdk_root / "build/.config")
differences = [{"key": key, "project": value, "sdk_generated": generated_cfg.get(key, "n")}
for key, value in sorted(source_cfg.items())
if generated_cfg and generated_cfg.get(key, "n") != value]
if not generated_cfg:
alerts.append({"code": "generated_config_missing", "message": "SDK build/.config 缺失或不可读"})
elif differences:
alerts.append({"code": "generated_config_differs", "message": "当前 SDK .config 与项目配置不同,需确认它属于哪次构建"})
project_partition = partition_name(source_cfg, flash)
sdk_partition = partition_name(generated_cfg, flash) if generated_cfg else None
if not (board_dir / "partition" / project_partition).is_file():
alerts.append({"code": "project_partition_missing", "message": f"项目配置选择的分区表不存在: {project_partition}"})
if sdk_partition and not (sdk_board / "partition" / sdk_partition).is_file():
alerts.append({"code": "sdk_partition_missing", "message": f"当前 SDK 配置选择的分区表不存在: {sdk_partition}"})
git_state = tracked_git_state(sdk_root)
if any(item["status"] == "dirty" for item in git_state):
alerts.append({"code": "dirty_sdk_repositories", "message": "关键 SDK 子仓库存在未提交的已跟踪文件改动"})
patch_script = root / "tools/BuildCvitek184XSDKTool.sh"
patch_code = sdk_root / "cvitek_cv184x_patch/scripts/repos"
tool_text = patch_script.read_text(encoding="utf-8") if patch_script.is_file() else ""
patch_text = patch_code.read_text(encoding="utf-8") if patch_code.is_file() else ""
auto_reset = "--applypatch --force" in tool_text and '"git", "reset", "--hard"' in patch_text
if auto_reset:
alerts.append({"code": "patch_force_reset_possible", "message": "补丁检查失败时构建脚本可能强制重置 SDK 子仓库;需先确认补丁状态及影响"})
if env.get("BUILD_SDK_DRIVER") == "n" and "build_osdrv" in tool_text and "BUILD_SDK_DRIVER" not in tool_text:
alerts.append({"code": "driver_switch_ignored", "message": "项目设置 BUILD_SDK_DRIVER=n,但当前 CV184X 专用工具未使用此变量,需按实际构建分支核对驱动"})
return {"schema_version": 1, "kind": "cv184x-sdk-readonly-inspection",
"project": str(project), "sdk_root": str(sdk_root), "chip": chip, "flash": flash,
"board_build_type": board, "tool_build_type": expected, "build_order": order,
"mappings": mappings, "generated_config_differences": differences,
"project_partition_candidate": project_partition, "sdk_partition_candidate": sdk_partition,
"inspected_git_repositories": git_state, "alerts": alerts,
"note": "只读磁盘快照;未执行同步、补丁检查、编译或板上验证。Git 状态仅覆盖列出的关键子仓库。"}
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())