feat: strengthen BSP and PSP validation and handoff

This commit is contained in:
tangfahong committed 2026-09-24 11:52:22 +08:00
1 parent bcea35a91e
commit 1641fac631
39 files changed
+2695 -90

No files matched your search

@@ -49,6 +49,64 @@ def detects_dual_os(config_dir: Path) -> bool:
return False
def resolve_board_layout(project: Path, chip: str | None = None) -> dict[str, object]:
"""Keep the existing CV184X layout; recognize CV181X split configuration."""
config_dirs = config_directories(project)
if chip and chip.upper().startswith("CV181"):
kernel = project / "configs" / "kernel"
uboot = project / "configs" / "uboot"
if {path.name for path in config_dirs} != {"kernel", "uboot"}:
return {
"board_build_type": None,
"system_form": None,
"config_directory": None,
"layout": "unknown",
"error": "CV181X 项目需要 configs/kernel 和 configs/uboot 两个配置目录",
}
candidates = sorted(
path for path in uboot.glob("*_defconfig")
if path.is_file() and not path.is_symlink() and not path.name.startswith("cvitek_")
)
if len(candidates) == 1:
build_type = candidates[0].name.removesuffix("_defconfig")
sdk_config = uboot / f"cvitek_{build_type}_defconfig"
kernel_config = kernel / f"cvitek_{build_type}_defconfig"
if (sdk_config.is_file() and not sdk_config.is_symlink()
and kernel_config.is_file() and not kernel_config.is_symlink()):
return {
"board_build_type": build_type,
"system_form": "dual-os" if detects_dual_os(uboot) else "single-os",
"config_directory": str(uboot),
"layout": "split-kernel-uboot",
"error": None,
}
return {
"board_build_type": None,
"system_form": None,
"config_directory": None,
"layout": "split-kernel-uboot",
"error": "CV181X kernel/uboot 结构未能唯一定位完整的板级 defconfig 组",
}
if len(config_dirs) == 1 and config_dirs[0].name not in {"kernel", "uboot"}:
config_dir = config_dirs[0]
return {
"board_build_type": config_dir.name,
"system_form": "dual-os" if detects_dual_os(config_dir) else "single-os",
"config_directory": str(config_dir),
"layout": "board-directory",
"error": None,
}
return {
"board_build_type": None,
"system_form": None,
"config_directory": None,
"layout": "unknown",
"error": f"无法识别配置布局:configs/ 下有 {[path.name for path in config_dirs]}",
}
def project_preflight(reference_name: str, target_name: str, root: Path) -> dict[str, object]:
errors: list[str] = []
warnings: list[str] = []
@@ -64,13 +122,19 @@ def project_preflight(reference_name: str, target_name: str, root: Path) -> dict
errors.append(f"缺少 buildEnvConfig.sh: {build_env}")
else:
assignments = parse_assignments(build_env)
config_dirs = config_directories(reference)
if len(config_dirs) != 1:
errors.append(f"参考项目必须唯一定位一个配置目录,实际为 {len(config_dirs)} 个")
config_dir = config_dirs[0] if len(config_dirs) == 1 else None
sync_script = reference / "configs" / "EPSYNCSDKConfig.sh"
if not sync_script.is_file():
warnings.append(f"未找到项目同步脚本: {sync_script}")
layout = resolve_board_layout(reference, assignments.get("EP_CHIP"))
if layout["error"]:
errors.append(str(layout["error"]))
if layout["layout"] == "split-kernel-uboot":
sync_scripts = [reference / "configs" / part / script for part, script in (
("kernel", "ep_kernel.sh"), ("uboot", "ep_uboot.sh"))]
sync_script = None
else:
sync_script = reference / "configs" / "EPSYNCSDKConfig.sh"
sync_scripts = [sync_script]
for path in sync_scripts:
if not path.is_file() or path.is_symlink():
warnings.append(f"未找到普通项目同步脚本: {path}")
imgs = reference / "imgs"
imgs_target = os.readlink(imgs) if imgs.is_symlink() else None
@@ -94,13 +158,15 @@ def project_preflight(reference_name: str, target_name: str, root: Path) -> dict
"architecture": assignments.get("EP_ARCH"),
"flash": assignments.get("EP_FLASH_TYPE"),
"nand_flash_id": assignments.get("NAND_FLASH_ID"),
"board_build_type": config_dir.name if config_dir else None,
"system_form": "dual-os" if config_dir and detects_dual_os(config_dir) else "single-os",
"board_build_type": layout["board_build_type"],
"system_form": layout["system_form"],
"config_layout": layout["layout"],
},
"bounded_paths": {
"build_env": str(build_env),
"config_directory": str(config_dir) if config_dir else None,
"sync_script": str(sync_script),
"config_directory": layout["config_directory"],
"sync_script": str(sync_script) if sync_script else None,
"sync_scripts": [str(path) for path in sync_scripts],
"reference_imgs_target": imgs_target,
"target_imgs_target": project_guard.expected_imgs_target(target_name),
},
@@ -115,6 +181,14 @@ def build_preflight(project_name: str, version: str, root: Path, output_root: Pa
if not VERSION_RE.fullmatch(version):
errors.append("构建版本必须为 YYYYMMDD_N,且 N 为正整数")
build_env = project / "buildEnvConfig.sh"
assignments = parse_assignments(build_env) if build_env.is_file() and not build_env.is_symlink() else {}
if not assignments.get("EP_CHIP"):
errors.append(f"构建前无法确认 EP_CHIP: {build_env}")
layout = resolve_board_layout(project, assignments.get("EP_CHIP"))
if layout["error"]:
errors.append(str(layout["error"]))
completion_script = root / "tools" / "script_completion.sh"
if not completion_script.is_file():
errors.append(f"统一构建入口不存在: {completion_script}")
@@ -137,6 +211,9 @@ def build_preflight(project_name: str, version: str, root: Path, output_root: Pa
f"EMX2_BuildSDKTool {project_name} {version} all"
)
warnings.append("统一构建可能同步配置到项目外 SDK,并按工具实现执行 git fetch origin")
requires_patch_precheck = assignments.get("EP_CHIP", "").upper().startswith("CV184")
if requires_patch_precheck:
warnings.append("CV184X 构建补丁检查失败可能触发 --applypatch --force;本预检未验证补丁环境,不能单凭 passed=true 自动构建")
return {
"schema_version": 1,
"kind": "build-preflight",
@@ -145,6 +222,9 @@ def build_preflight(project_name: str, version: str, root: Path, output_root: Pa
"warnings": warnings,
"project": project_name,
"project_path": str(project),
"config_layout": layout["layout"],
"board_build_type": layout["board_build_type"],
"requires_patch_precheck": requires_patch_precheck,
"build_version": version,
"working_directory": str(root),
"completion_script": str(completion_script),
@@ -0,0 +1,182 @@
#!/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())
@@ -0,0 +1,210 @@
#!/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())
@@ -8,6 +8,7 @@ import hashlib
import json
import os
import shlex
import stat
import subprocess
import tempfile
import xml.etree.ElementTree as ET
@@ -20,6 +21,9 @@ import project_guard
REQUIRED_IMAGE_MARGIN_BYTES = 512 * 1024
REQUIRED_PARTITION_IMAGES = ("kernel.bin", "rootfs.bin")
REQUIRED_DIRECTORY_ACCESS_BITS = 0o555
REQUIRED_FILE_READ_BITS = 0o444
ALL_EXECUTE_BITS = 0o111
def now_utc() -> str:
@@ -113,6 +117,119 @@ def artifact_inventory(output_directory: Path) -> tuple[list[dict[str, object]],
return artifacts, warnings
def ensure_output_permissions(
output_root: Path, output_directory: Path
) -> dict[str, object]:
"""Apply and verify a+rX on one build output path without following symlinks."""
output_root = output_root.expanduser().absolute()
if output_root.is_symlink():
raise project_guard.GuardError(f"构建输出根目录是软链接: {output_root}")
output_root = output_root.resolve(strict=True)
output_directory = output_directory.expanduser().absolute()
if not output_root.is_dir() or output_root.is_symlink():
raise project_guard.GuardError(f"构建输出根目录不是普通目录: {output_root}")
try:
relative_output = output_directory.relative_to(output_root)
except ValueError as exc:
raise project_guard.GuardError(
f"构建输出目录不在约定输出根目录内: {output_directory}"
) from exc
changes: list[dict[str, object]] = []
checked = {
"path_directories": 0,
"tree_directories": 0,
"regular_files": 0,
"symlinks_skipped": 0,
}
def apply_required_bits(path: Path, required_bits: int, kind: str) -> None:
if path.is_symlink():
raise project_guard.GuardError(f"权限检查拒绝软链接路径节点: {path}")
try:
before = stat.S_IMODE(path.stat().st_mode)
requested = before | required_bits
if requested != before:
os.chmod(path, requested)
after = stat.S_IMODE(path.stat().st_mode)
except OSError as exc:
raise project_guard.GuardError(f"无法设置构建输出权限 {path}: {exc}") from exc
if after & required_bits != required_bits:
raise project_guard.GuardError(
f"构建输出权限不满足 {kind} 要求: {path},"
f"实际 {after:04o},至少需要 {required_bits:04o}"
)
if after != before:
changes.append(
{
"path": str(path),
"kind": kind,
"before_mode": f"{before:04o}",
"after_mode": f"{after:04o}",
}
)
current = output_root
path_directories = [current]
for part in relative_output.parts:
current = current / part
path_directories.append(current)
for path in path_directories:
if not path.is_dir() or path.is_symlink():
raise project_guard.GuardError(f"构建输出路径节点不是普通目录: {path}")
apply_required_bits(path, REQUIRED_DIRECTORY_ACCESS_BITS, "path_directory")
checked["path_directories"] += 1
def walk_error(exc: OSError) -> None:
raise project_guard.GuardError(f"无法遍历构建输出目录: {exc}") from exc
for parent, directory_names, file_names in os.walk(
output_directory, followlinks=False, onerror=walk_error
):
parent_path = Path(parent)
for name in list(directory_names):
path = parent_path / name
if path.is_symlink():
directory_names.remove(name)
checked["symlinks_skipped"] += 1
continue
if not path.is_dir():
raise project_guard.GuardError(f"构建输出包含非普通目录: {path}")
apply_required_bits(path, REQUIRED_DIRECTORY_ACCESS_BITS, "tree_directory")
checked["tree_directories"] += 1
for name in file_names:
path = parent_path / name
if path.is_symlink():
checked["symlinks_skipped"] += 1
continue
if not path.is_file():
raise project_guard.GuardError(f"构建输出包含非普通文件: {path}")
before = stat.S_IMODE(path.stat().st_mode)
required = REQUIRED_FILE_READ_BITS
if before & ALL_EXECUTE_BITS:
required |= ALL_EXECUTE_BITS
apply_required_bits(path, required, "regular_file")
checked["regular_files"] += 1
return {
"schema_version": 1,
"passed": True,
"checked_at": now_utc(),
"policy": "a+rX",
"output_root": str(output_root),
"imgs_version_path": str(output_directory),
"checked": checked,
"changed_count": len(changes),
"changes": changes,
"notes": [
"目录和输出路径节点必须对所有用户可读、可进入。",
"普通文件必须对所有用户可读;只有原本可执行的文件才向所有用户补执行权限。",
"不跟随软链接,也不修改软链接目标。",
],
}
def resolve_partition_table(project: Path, requested: Path | None) -> Path:
configs = project / "configs"
if not configs.is_dir() or configs.is_symlink():
@@ -258,10 +375,14 @@ def record_handoff(
root = project_guard.normalize_root(root)
project = project_guard.direct_child(root, project_name, "project", True)
output_root = output_root.expanduser().resolve(strict=False)
output_root = output_root.expanduser().absolute()
if output_root.is_symlink():
raise project_guard.GuardError(f"构建输出根目录是软链接: {output_root}")
output_root = output_root.resolve(strict=False)
output_directory = output_root / project_name / "imgs" / build_version
if not output_directory.is_dir() or output_directory.is_symlink():
raise project_guard.GuardError(f"最终 imgs 版本目录不存在或不是普通目录: {output_directory}")
output_permission_check = ensure_output_permissions(output_root, output_directory)
artifacts, warnings = artifact_inventory(output_directory)
if not artifacts:
raise project_guard.GuardError("最终 imgs 版本目录没有非空普通文件")
@@ -272,13 +393,10 @@ def record_handoff(
build_env = project / "buildEnvConfig.sh"
assignments = bsp_preflight.parse_assignments(build_env) if build_env.is_file() else {}
config_dirs = bsp_preflight.config_directories(project)
config_dir = config_dirs[0] if len(config_dirs) == 1 else None
if len(config_dirs) != 1:
warnings.append(f"无法唯一确定 board build type:找到 {len(config_dirs)} 个配置目录")
system_form = (
"dual-os" if config_dir is not None and bsp_preflight.detects_dual_os(config_dir) else "single-os"
)
layout = bsp_preflight.resolve_board_layout(project, assignments.get("EP_CHIP"))
if layout["error"]:
raise project_guard.GuardError(str(layout["error"]))
system_form = layout["system_form"]
command = build_command or (
f"cd {shlex.quote(str(root))} && source tools/script_completion.sh && "
f"EMX2_BuildSDKTool {project_name} {build_version} all"
@@ -288,6 +406,7 @@ def record_handoff(
handoff_dir = project / "doc" / "bsp_adaptation" / "handoff"
hash_path = validation_dir / f"artifact_manifest_{build_version}.sha256"
margin_path = validation_dir / f"image_partition_margin_{build_version}.json"
permission_path = validation_dir / f"output_permissions_{build_version}.json"
hash_lines = "".join(
f"{item['sha256']} {item['relative_path']}\n" for item in artifacts
)
@@ -295,6 +414,9 @@ def record_handoff(
write_text_atomic(
margin_path, json.dumps(image_partition_check, ensure_ascii=False, indent=2) + "\n"
)
write_text_atomic(
permission_path, json.dumps(output_permission_check, ensure_ascii=False, indent=2) + "\n"
)
manifest: dict[str, object] = {
"schema_version": 1,
@@ -312,7 +434,7 @@ def record_handoff(
"sdk": assignments.get("EP_SDK_VER"),
"architecture": assignments.get("EP_ARCH"),
"flash": assignments.get("EP_FLASH_TYPE"),
"board_build_type": config_dir.name if config_dir else None,
"board_build_type": layout["board_build_type"],
"system_form": system_form,
},
"build": {
@@ -326,11 +448,13 @@ def record_handoff(
"imgs_version_path": str(output_directory),
"artifact_manifest_path": str(hash_path),
"image_partition_margin_path": str(margin_path),
"output_permissions_path": str(permission_path),
},
"artifacts": artifacts,
"validation": {
"local_build": "passed",
"image_partition_margin": image_partition_check,
"output_permissions": output_permission_check,
"hardware": "pending",
"owner_acceptance": "pending",
"release": "not-authorized",
@@ -342,7 +466,7 @@ def record_handoff(
"immutable_artifacts": True,
"verify_sha256_before_use": True,
"select_latest_directory": False,
"baseline_change_rule": "Any project, build version, artifact path/size/hash, partition table/hash, or image-margin change invalidates downstream PSP validation.",
"baseline_change_rule": "Any project, build version, artifact path/size/hash, partition table/hash, output-permission evidence, or image-margin change invalidates downstream PSP validation.",
},
}
handoff_path = handoff_dir / "psp_handoff.json"
@@ -358,6 +482,8 @@ def record_handoff(
"psp_handoff_path": str(handoff_path),
"image_partition_margin_path": str(margin_path),
"image_partition_margin": image_partition_check,
"output_permissions_path": str(permission_path),
"output_permissions": output_permission_check,
"hardware_validation": "pending",
"owner_acceptance": "pending",
"release": "not-authorized",
@@ -371,6 +497,8 @@ def record_handoff(
"artifact_manifest_path": str(hash_path),
"image_partition_margin_path": str(margin_path),
"image_partition_margin": image_partition_check,
"output_permissions_path": str(permission_path),
"output_permissions": output_permission_check,
"psp_handoff_path": str(handoff_path),
"status_path": str(status_path),
"warnings": warnings,
@@ -3,8 +3,11 @@
import hashlib
import json
import os
import stat
import tempfile
from pathlib import Path
from unittest.mock import patch
import bsp_preflight
import project_guard
@@ -72,6 +75,19 @@ def main() -> int:
kernel.write_bytes(b"k" * (512 * 1024))
rootfs = output / "rootfs.bin"
rootfs.write_bytes(b"r" * (256 * 1024))
helper_dir = output / "tools"
helper_dir.mkdir()
helper = helper_dir / "inspect.sh"
helper.write_text("#!/bin/sh\n", encoding="utf-8")
os.chmod(output_root, 0o700)
os.chmod(output_root / reference.name, 0o700)
os.chmod(output_root / reference.name / "imgs", 0o700)
os.chmod(output, 0o700)
os.chmod(helper_dir, 0o700)
os.chmod(boot, 0o600)
os.chmod(kernel, 0o640)
os.chmod(rootfs, 0o600)
os.chmod(helper, 0o700)
conflict = bsp_preflight.build_preflight(
reference.name, "20260902_1", root, output_root
)
@@ -95,6 +111,21 @@ def main() -> int:
assert recorded["passed"] is True, recorded
assert recorded["imgs_version_path"] == str(output.resolve())
assert recorded["image_partition_margin"]["passed"] is True
assert recorded["output_permissions"]["passed"] is True
assert recorded["output_permissions"]["policy"] == "a+rX"
assert recorded["output_permissions"]["changed_count"] >= 1
for directory in (
output_root,
output_root / reference.name,
output_root / reference.name / "imgs",
output,
helper_dir,
):
assert stat.S_IMODE(directory.stat().st_mode) & 0o555 == 0o555
for artifact in (boot, kernel, rootfs, helper):
assert stat.S_IMODE(artifact.stat().st_mode) & 0o444 == 0o444
assert stat.S_IMODE(boot.stat().st_mode) & 0o111 == 0
assert stat.S_IMODE(helper.stat().st_mode) & 0o111 == 0o111
handoff_path = Path(recorded["psp_handoff_path"])
handoff = json.loads(handoff_path.read_text(encoding="utf-8"))
assert handoff["handoff_type"] == "bsp-to-psp"
@@ -109,10 +140,24 @@ def main() -> int:
assert kernel_check["actual_margin_bytes"] == 512 * 1024
assert kernel_check["effective_partition_size_bytes"] == 1024 * 1024
assert Path(recorded["image_partition_margin_path"]).is_file()
assert Path(recorded["output_permissions_path"]).is_file()
assert handoff["validation"]["output_permissions"]["passed"] is True
assert handoff["build"]["output_permissions_path"] == recorded["output_permissions_path"]
assert handoff["consumer_contract"]["select_latest_directory"] is False
assert handoff["artifacts"][0]["sha256"] == hashlib.sha256(boot.read_bytes()).hexdigest()
assert handoff["validation"]["hardware"] == "pending"
linked_output_root = Path(tmp) / "linked-output"
linked_output_root.symlink_to(output_root, target_is_directory=True)
try:
record_build_handoff.record_handoff(
reference.name, "20260902_1", 0, True, root, linked_output_root, None, []
)
except project_guard.GuardError as exc:
assert "输出根目录是软链接" in str(exc)
else:
raise AssertionError("linked output root must not be granted permissions")
overflow_output = output_root / reference.name / "imgs" / "20260902_2"
overflow_output.mkdir(parents=True)
(overflow_output / "boot.bin").write_bytes(b"boot")
@@ -141,6 +186,41 @@ def main() -> int:
/ "image_partition_margin_20260902_2.json"
).exists()
denied_output = output_root / reference.name / "imgs" / "20260902_3"
denied_output.mkdir(parents=True)
denied_boot = denied_output / "boot.bin"
denied_boot.write_bytes(b"boot")
(denied_output / "kernel.bin").write_bytes(b"k" * (512 * 1024))
(denied_output / "rootfs.bin").write_bytes(b"r" * (256 * 1024))
os.chmod(denied_boot, 0o600)
with patch.object(
record_build_handoff.os,
"chmod",
side_effect=PermissionError(13, "permission denied"),
):
try:
record_build_handoff.record_handoff(
reference.name,
"20260902_3",
0,
True,
root,
output_root,
None,
[],
)
except project_guard.GuardError as exc:
assert "无法设置构建输出权限" in str(exc)
else:
raise AssertionError("permission failure must block BSP handoff")
assert not (
reference
/ "doc"
/ "bsp_adaptation"
/ "validation"
/ "output_permissions_20260902_3.json"
).exists()
original_partition = (
reference
/ "configs"
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Regression checks for CV181X split layout and opt-in read-only SDK mapping."""
from pathlib import Path
from tempfile import TemporaryDirectory
import bsp_preflight
import inspect_cv181x_sdk
def write(path: Path, value: str = "test\n") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(value, encoding="utf-8")
def main() -> int:
with TemporaryDirectory() as temporary:
root = Path(temporary) / "EmxRes"
root.mkdir()
board = "cv1815ja_wevb_0020a_spinand"
project = root / "CV1815JA-TEST"
project.mkdir()
write(project / "buildEnvConfig.sh", "EP_CHIP=CV1815JA\nEP_COMPANY=Cvitek\nEP_FLASH_TYPE=nand\nEP_SDK_VER=v4.1.6_beta\n")
write(project / "configs/uboot" / f"{board}_defconfig", "CONFIG_ENABLE_FREERTOS=y\nCONFIG_CHIP_CV1815JA=y\n")
write(project / "configs/uboot" / f"cvitek_{board}_defconfig")
write(project / "configs/kernel" / f"cvitek_{board}_defconfig")
write(project / "configs/uboot/cvi_board_init.c")
write(project / "configs/uboot/partition_spinand.xml")
write(project / "configs/kernel/memmap.py")
write(project / "configs/kernel" / f"{board}.dts")
write(project / "configs/kernel/cv181x_base.dtsi")
write(project / "configs/kernel/ep_kernel.sh", "\n".join((
"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/",
)))
write(project / "configs/uboot/ep_uboot.sh", "\n".join((
"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",
)))
write(root / "tools/BuildCvitekSDKTool.sh", "defconfig $CV_BUILD_TYPE\nEp_Uboot_Config_Func\nEp_Kernel_Config_Func\n")
layout = bsp_preflight.resolve_board_layout(project, "CV1815JA")
assert layout["layout"] == "split-kernel-uboot", layout
assert layout["board_build_type"] == board, layout
assert layout["system_form"] == "single-os", layout
report = bsp_preflight.project_preflight(project.name, "CV1815JA-NEW", root)
assert report["passed"] is True, report
assert report["facts"]["config_layout"] == "split-kernel-uboot", report
assert len(report["bounded_paths"]["sync_scripts"]) == 2, report
assert bsp_preflight.resolve_board_layout(project, "CV1841CP")["error"]
write(root / "tools/script_completion.sh")
project.joinpath("imgs").symlink_to(f"/epbuild/EMX2.0/EmxRes/{project.name}/imgs")
build_report = bsp_preflight.build_preflight(project.name, "20260923_1", root, Path(temporary) / "output")
assert build_report["passed"] is True, build_report
assert build_report["config_layout"] == "split-kernel-uboot", build_report
assert build_report["requires_patch_precheck"] is False, build_report
sdk = Path(temporary) / "v4.1.6_beta"
for source_rel, dest_rel in inspect_cv181x_sdk.mapped_files(board, project):
write(sdk / dest_rel, (project / source_rel).read_text(encoding="utf-8"))
shared = sdk / "build/boards/default/memmap/shared.py"
write(shared, "test\n")
memmap = sdk / "build/boards/cv181x" / board / "memmap.py"
memmap.unlink()
memmap.symlink_to(shared)
write(sdk / "build/.config", "CONFIG_CHIP_CV1815JA=n\n")
result = inspect_cv181x_sdk.inspect(project, sdk)
codes = {item["code"] for item in result["alerts"]}
assert {"shared_sdk_target", "generated_config_differs", "sync_after_defconfig"} <= codes, codes
assert len(result["mappings"]) == 8, result
try:
inspect_cv181x_sdk.inspect(project, Path(temporary))
except ValueError as exc:
assert "EP_SDK_VER" in str(exc)
else:
raise AssertionError("mismatched SDK root must fail")
(project / "configs/kernel" / f"cvitek_{board}_defconfig").unlink()
assert bsp_preflight.resolve_board_layout(project, "CV1815JA")["error"]
assert bsp_preflight.build_preflight(project.name, "20260923_2", root, Path(temporary) / "output")["passed"] is False
cv184 = root / "CV1841CP-TEST"
write(cv184 / "configs/cv1841cp_wevb_0015a_spinand/board_defconfig")
old = bsp_preflight.resolve_board_layout(cv184, "CV1841CP")
assert old["layout"] == "board-directory", old
assert old["board_build_type"] == "cv1841cp_wevb_0015a_spinand", old
assert bsp_preflight.resolve_board_layout(cv184, "CV1815JA")["error"]
write(cv184 / "buildEnvConfig.sh", "EP_CHIP=CV1841CP\n")
cv184.joinpath("imgs").symlink_to(f"/epbuild/EMX2.0/EmxRes/{cv184.name}/imgs")
cv184_build = bsp_preflight.build_preflight(cv184.name, "20260923_1", root, Path(temporary) / "output")
assert cv184_build["requires_patch_precheck"] is True, cv184_build
assert any("--applypatch --force" in item for item in cv184_build["warnings"]), cv184_build
print("CV181X layout and SDK read-only inspection tests passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Behavior tests for the bounded CV184X read-only SDK inspector."""
from pathlib import Path
from tempfile import TemporaryDirectory
import inspect_cv184x_sdk
def write(path: Path, value: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(value, encoding="utf-8")
def main() -> int:
with TemporaryDirectory() as temporary:
root = Path(temporary) / "EmxRes"
board = "cv1841cp_wevb_0015a_spinand"
project = root / "CV1841CP-TEST"
sdk = Path(temporary) / "cv184x_v6.3.3"
write(project / "buildEnvConfig.sh", "EP_COMPANY=Cvitek\nEP_CHIP=CV1841CP\nEP_FLASH_TYPE=nand\nEP_SDK_VER=cv184x_v6.3.3\nBUILD_SDK_DRIVER=n\n")
write(project / "configs/EPSYNCSDKConfig.sh",
"EP_UBOOT_BUILD_TOP=build/boards/cv184x/$CV_BUILD_TYPE # known board\n"
f"rsync -av $EP_CONFIGS_PATH/{board}/. $EP_UBOOT_BUILD_TOP/\nexit 0\n")
source = project / "configs" / board
write(source / f"{board}_defconfig", "CONFIG_DUAL_OS=y\nCONFIG_ION_SIZE=0x600000\n")
write(source / "partition/partition_spinand.xml", "<physical_partition/>\n")
write(root / "tools/BuildCvitek184XSDKTool.sh",
'if [[ $EP_CHIP == "CV1841CP" ]];then\n'
' if [[ $EP_FLASH_TYPE == "nor" ]];then\n'
' CV_BUILD_TYPE=cv1841cp_wevb_0015a_spinor\n'
' elif [[ $EP_FLASH_TYPE == "nand" ]];then\n'
f' CV_BUILD_TYPE={board}\n'
' fi\nfi\nexport CV_BUILD_TYPE\n'
'Ep_Sync_SDK_Config_Func\ndefconfig $CV_BUILD_TYPE\n'
'./cvitek_cv184x_patch/scripts/repos --applypatch --force\nbuild_osdrv\n')
write(sdk / "cvitek_cv184x_patch/scripts/repos", 'run_cmd(["git", "reset", "--hard", expected_commit])\n')
write(sdk / "build/boards/cv184x" / board / f"{board}_defconfig", "CONFIG_DUAL_OS=n\n")
write(sdk / "build/boards/cv184x" / board / "partition/partition_spinand.xml", "<physical_partition/>\n")
write(sdk / "build/.config", "CONFIG_DUAL_OS=n\nCONFIG_ION_SIZE=0x600000\n")
result = inspect_cv184x_sdk.inspect(project, sdk)
assert result["tool_build_type"] == board, result
assert result["build_order"] == "sync-before-defconfig", result
assert len(result["mappings"]) == 2, result
assert result["project_partition_candidate"] == "partition_spinand.xml", result
codes = {item["code"] for item in result["alerts"]}
assert {"sdk_copy_differs", "generated_config_differs", "patch_force_reset_possible", "driver_switch_ignored"} <= codes, codes
assert "build_type_mismatch" not in codes, codes
assert inspect_cv184x_sdk.partition_name({"CONFIG_ROOTFS_B": "y"}, "nand") == "partition_ab_spinand.xml"
assert inspect_cv184x_sdk.partition_name({"CONFIG_ROOTFS_B": "y", "CONFIG_ROOTFS_RECOVERY": "y"}, "nand") == "partition_abr_spinand.xml"
write(root / "tools/BuildCvitek184XSDKTool.sh",
'if [[ $EP_CHIP == "CV1841CP" ]];then\n'
' if [[ $EP_FLASH_TYPE == "nand" ]];then\n'
' CV_BUILD_TYPE=cv1841cp_other_spinand\n'
' fi\nfi\nexport CV_BUILD_TYPE\n'
'defconfig $CV_BUILD_TYPE\nEp_Sync_SDK_Config_Func\n')
mismatch = inspect_cv184x_sdk.inspect(project, sdk)
codes = {item["code"] for item in mismatch["alerts"]}
assert {"build_type_mismatch", "build_order_unknown"} <= codes, codes
write(project / "configs/EPSYNCSDKConfig.sh", "rsync -av $EP_CONFIGS_PATH/unknown/. $EP_UBOOT_BUILD_TOP/\n")
try:
inspect_cv184x_sdk.inspect(project, sdk)
except ValueError as exc:
assert "同步脚本" in str(exc)
else:
raise AssertionError("unknown mapping must fail closed")
try:
inspect_cv184x_sdk.inspect(project, Path(temporary))
except ValueError as exc:
assert "EP_SDK_VER" in str(exc)
else:
raise AssertionError("wrong SDK root must fail")
print("CV184X read-only SDK inspection tests passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())