#!/usr/bin/env python3 """Verify package-smoke wrappers report the expected package readiness matrix.""" from __future__ import annotations import json import re import sys from pathlib import Path EXPECTED_PACKAGE_KINDS = [ "windows-appx", "android-standard-apk", "android-quest-apk", "android-focus-apk", "apple-bundle", "linux-app", "webgl", ] EXPECTED_CMAKE_PACKAGE_TARGETS = [ "panopainter_package_readiness", "panopainter_windows_app_package_smoke", "panopainter_windows_appx_package_readiness", "panopainter_apple_bundle_package_readiness", "panopainter_android_standard_native_package", "panopainter_android_standard_apk_package_readiness", "panopainter_android_quest_apk_package_readiness", "panopainter_android_focus_apk_package_readiness", "panopainter_android_vr_native_package_configure", "panopainter_android_native_package_smoke", "panopainter_linux_app_package_readiness", "panopainter_webgl_package_readiness", "panopainter_linux_webgl_package_readiness", ] def repo_root() -> Path: return Path(__file__).resolve().parents[2] def powershell_package_kinds(root: Path) -> list[str]: script = (root / "scripts" / "automation" / "package-smoke.ps1").read_text(encoding="utf-8") match = re.search(r"\[string\[\]\]\$PackageKinds\s*=\s*@\((.*?)\n\s*\)", script, re.S) if not match: raise RuntimeError("Could not find PackageKinds default in package-smoke.ps1") return sorted(re.findall(r'"([^"]+)"', match.group(1))) def shell_package_kinds(root: Path) -> list[str]: script = (root / "scripts" / "automation" / "package-smoke.sh").read_text(encoding="utf-8") match = re.search(r'package_kinds="([^"]+)"', script) if match: return sorted(set(filter(None, (value.strip() for value in match.group(1).split(","))))) quoted_kinds = sorted(set(re.findall(r'"kind":"([^"]+)"', script))) escaped_kinds = sorted(set(re.findall(r'\\"kind\\":\\"([^\\"]+)\\"', script))) if quoted_kinds or escaped_kinds: return sorted(set(quoted_kinds).union(escaped_kinds)) raise RuntimeError("Could not find package kinds defaults in package-smoke.sh") def count_regex(root: Path, patterns: dict[str, str]) -> dict[str, int]: counts: dict[str, int] = {} for script_name, pattern in patterns.items(): text = (root / "scripts" / "automation" / script_name).read_text(encoding="utf-8") counts[script_name] = len(re.findall(pattern, text)) return counts def main() -> int: root = repo_root() expected = sorted(EXPECTED_PACKAGE_KINDS) ps_kinds = powershell_package_kinds(root) sh_kinds = shell_package_kinds(root) script_texts = { "package-smoke.ps1": (root / "scripts" / "automation" / "package-smoke.ps1").read_text(encoding="utf-8"), "package-smoke.sh": (root / "scripts" / "automation" / "package-smoke.sh").read_text(encoding="utf-8"), } debt_counts = count_regex(root, { "package-smoke.ps1": r'debt\s*=\s*"DEBT-0011"', "package-smoke.sh": r'\\"debt\\":\\"DEBT-0011\\"', }) status_tokens = ("blocked", "compile-only", "validated") status_modes = { name: [token for token in status_tokens if f'"{token}"' in text] for name, text in script_texts.items() } status_mode_present = { name: { token: f'"{token}"' in script_texts[name] for token in ("blocked", "compile-only") } for name in ("package-smoke.ps1", "package-smoke.sh") } readiness_alignment = count_regex(root, { "package-smoke.ps1": r'androidNativeValidation', "package-smoke.sh": r'androidNativeValidation', }) readiness_mode_counts = { "package-smoke.ps1": (root / "scripts" / "automation" / "package-smoke.ps1").read_text(encoding="utf-8").count("ReadinessOnly"), "package-smoke.sh": (root / "scripts" / "automation" / "package-smoke.sh").read_text(encoding="utf-8").count("readiness_only"), } retained_android_native_counts = count_regex(root, { "package-smoke.ps1": r"retained-native-cmake-check", "package-smoke.sh": r"retained-native-cmake-check", }) retained_platform_cmake_counts = count_regex(root, { "package-smoke.ps1": r"retained-(linux|webgl)-cmake", "package-smoke.sh": r"retained-(linux|webgl)-cmake", }) cmake_package_module = (root / "cmake" / "PanoPainterPackageTargets.cmake").read_text(encoding="utf-8") root_cmake = (root / "CMakeLists.txt").read_text(encoding="utf-8") missing = { "package-smoke.ps1": [kind for kind in expected if kind not in ps_kinds], "package-smoke.sh": [kind for kind in expected if kind not in sh_kinds], } unexpected = { "package-smoke.ps1": [kind for kind in ps_kinds if kind not in expected], "package-smoke.sh": [kind for kind in sh_kinds if kind not in expected], } debt_thresholds = { "package-smoke.ps1": 1, "package-smoke.sh": len(expected), } debt_complete = {name: count >= debt_thresholds[name] for name, count in debt_counts.items()} status_gate_complete = { "package-smoke.ps1": status_mode_present["package-smoke.ps1"]["blocked"] and status_mode_present["package-smoke.ps1"]["compile-only"], "package-smoke.sh": status_mode_present["package-smoke.sh"]["blocked"] and status_mode_present["package-smoke.sh"]["compile-only"], } readiness_mode_present = {name: count > 0 for name, count in readiness_mode_counts.items()} retained_android_native_complete = { name: count >= 3 for name, count in retained_android_native_counts.items() } retained_platform_cmake_complete = { name: count >= 2 for name, count in retained_platform_cmake_counts.items() } cmake_package_targets_present = { target: target in cmake_package_module for target in EXPECTED_CMAKE_PACKAGE_TARGETS } cmake_package_module_included = "include(PanoPainterPackageTargets)" in root_cmake ok = ( all(not values for values in missing.values()) and all(not values for values in unexpected.values()) and all(debt_complete.values()) and all(status_gate_complete.values()) and all(readiness_alignment.values()) and all(readiness_mode_present.values()) and all(retained_android_native_complete.values()) and all(retained_platform_cmake_complete.values()) and all(cmake_package_targets_present.values()) and cmake_package_module_included ) print(json.dumps({ "ok": ok, "expectedPackageKinds": expected, "packageKinds": { "package-smoke.ps1": ps_kinds, "package-smoke.sh": sh_kinds, }, "missing": missing, "unexpected": unexpected, "debtComplete": debt_complete, "statusModes": status_modes, "statusModePresent": status_mode_present, "readinessAlignment": readiness_alignment, "readinessModePresent": readiness_mode_present, "retainedAndroidNativeComplete": retained_android_native_complete, "retainedPlatformCmakeComplete": retained_platform_cmake_complete, "cmakePackageTargetsPresent": cmake_package_targets_present, "cmakePackageModuleIncluded": cmake_package_module_included, }, separators=(",", ":"))) return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())