Add validated OTA candidate publishing and fix 1.1.2 dependency failure

This commit is contained in:
2026-09-27 19:00:20 +08:00
parent 98eadc5c8b
commit f80c4d9938
26 changed files with 571 additions and 25 deletions
@@ -23,6 +23,7 @@ from app.ota.policy import export_bundle, package_format, release_metadata, read
from scripts.build_sd_image import build_image, sha256_file
from scripts.fat16_image import Fat16Image
from scripts.image_config import create_config_file, read_config_file
from scripts.offline_wheels import validate_offline_wheels
def next_patch(version: SoftwareVersion) -> SoftwareVersion:
@@ -63,7 +64,7 @@ def _ota_readme(version: SoftwareVersion, manifest: dict, notes: str) -> str:
"在系统设置上传 OTA;每个 minor 的 .0 必须按顺序安装,同系列补丁允许跳过。"
"1.1.0 含 frpc 0.71.0 离线组件;已有完整组件会跳过,否则修复并保留配置和启停状态。"
"初次安装默认关闭。禁止跳过失败测试;失败自动恢复。\n\n"
"本目录是可删除的发布产物,不得被源码、脚本或后续版本引用。\n"
"本目录作为 OTA 发布产物长期保留,不得被源码、脚本或后续版本当作构建输入。\n"
)
@@ -187,7 +188,7 @@ def main() -> int:
parser.add_argument("--notes", required=True)
parser.add_argument("--version", type=SoftwareVersion.parse, help="explicit release version, e.g. 1.1.0")
parser.add_argument("--config", type=Path, help="required JSON provisioning config for image export")
parser.add_argument("--candidate-output", type=Path, help="build a verified image candidate directory without publishing")
parser.add_argument("--candidate-output", type=Path, help="build an OTA or image candidate directory without publishing")
parser.add_argument("--validated-candidate", type=Path, help="exact real-device validated OTA file or image candidate directory")
parser.add_argument("--validation-report", type=Path, help="matching real-device validation JSON")
parser.add_argument(
@@ -204,6 +205,15 @@ def main() -> int:
from scripts.repair_ota_release import promote
print(promote(SOURCE_ROOT, args.validated_candidate, args.validation_report, args.notes))
return 0
ota_promotion = args.kind == "ota" and args.validated_candidate is not None
if ota_promotion:
if args.validation_report is None:
parser.error("OTA candidate promotion requires --validation-report")
if args.config is not None or args.version is not None or args.candidate_output is not None:
parser.error("OTA candidate promotion cannot use --config, --version, or --candidate-output")
from scripts.promote_ota_release import promote
print(promote(SOURCE_ROOT, args.validated_candidate, args.validation_report, args.notes))
return 0
image_promotion = args.kind == "image" and args.validated_candidate is not None
if image_promotion:
if args.validation_report is None:
@@ -218,13 +228,15 @@ def main() -> int:
print(promote(SOURCE_ROOT, args.validated_candidate, args.validation_report, args.notes))
return 0
if args.validated_candidate is not None or args.validation_report is not None:
parser.error("candidate validation options require OTA --repair-current or image promotion")
parser.error("candidate validation options require OTA or image promotion")
if args.kind == "image" and args.config is None and not args.repair_current:
parser.error("--config is required for image candidate export")
if args.kind == "image" and not args.repair_current and args.candidate_output is None:
parser.error("new image releases must use --candidate-output and pass real-card validation before publishing")
if args.kind == "ota" and args.candidate_output is not None:
parser.error("--candidate-output is only supported for image export")
if args.kind == "ota" and args.config is not None:
parser.error("--config is only supported for image export")
if args.kind == "ota" and args.candidate_output is None:
parser.error("new OTA releases must use --candidate-output and real-device validation before publishing")
if args.repair_current and args.config is not None:
parser.error("--repair-current reuses the registered image configuration; do not pass --config")
if args.kind == "image":
@@ -247,6 +259,7 @@ def main() -> int:
dependency_bundle = None
if not args.repair_current and args.kind == "ota":
dependency_bundle = export_bundle(source, project / "发布更新相关" / "其他依赖", current, target)
validate_offline_wheels(source, project / "发布更新相关" / "其他依赖" / "aarch64-py311")
if args.kind == "image" and not args.repair_current and target <= current:
parser.error("image candidate version must be newer than the current version")
output_root = project / "发布更新相关" / "OTA数据包" if args.kind == "ota" else project / "发布更新相关" / "导出包"
@@ -258,11 +271,11 @@ def main() -> int:
except ValueError:
pass
else:
parser.error("candidate output must be outside the formal image release directory")
parser.error("candidate output must be outside the formal release directory")
if final_directory.exists() and not args.repair_current:
parser.error(f"release directory already exists: {final_directory}")
if official_directory.exists() and args.candidate_output is not None:
parser.error(f"formal image release directory already exists: {official_directory}")
parser.error(f"formal release directory already exists: {official_directory}")
if args.repair_current and not final_directory.is_dir():
parser.error(f"current image release directory is missing: {final_directory}")
output_root.mkdir(parents=True, exist_ok=True)