diff options
Diffstat (limited to 'tools/gen_products.py')
| -rw-r--r-- | tools/gen_products.py | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/tools/gen_products.py b/tools/gen_products.py new file mode 100644 index 0000000..7504287 --- /dev/null +++ b/tools/gen_products.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# Author: cirakg & claude ai +# Summary: Check that every product in manifest.xml is a watch with API 5.0+ and at least 512 KB for watch apps. +"""Check the manifest's product list against the downloaded device files. + +The target rule (README section 14, decision D4): a product belongs in +manifest.xml when it is in Garmin's "Watches/Wearables" group, every firmware +variant supports Connect IQ API 5.0.0 or newer, and watch apps get at least +512 KB of memory. The data comes from each device's compiler.json, which +`make sdk` downloads for exactly the products in the manifest. + +Usage: gen_products.py --check [--manifest manifest.xml] [--devices DIR] +Exit: 0 every product qualifies, 1 some don't, 2 device files missing. + +Finding devices Garmin releases later needs the device catalogue (a login); +re-run `make sdk` after adding a new product to the manifest. +""" + +from __future__ import annotations + +import argparse +import json +import sys +import xml.etree.ElementTree as ET +from dataclasses import dataclass +from pathlib import Path + +EXIT_OK, EXIT_UNQUALIFIED, EXIT_MISSING = 0, 1, 2 +IQ_NS = "{http://www.garmin.com/xml/connectiq}" +MIN_API = (5, 0, 0) +MIN_WATCH_APP_MEMORY = 512 * 1024 +WATCH_GROUP = "Watches/Wearables" +DEFAULT_DEVICES = Path.home() / ".Garmin" / "ConnectIQ" / "Devices" + + +@dataclass(frozen=True) +class Device: + """What the target rule needs from one compiler.json.""" + + product: str + name: str + group: str + min_api: tuple[int, ...] + watch_app_memory: int + + def problems(self) -> list[str]: + reasons = [] + if self.group != WATCH_GROUP: + reasons.append(f"group is '{self.group}', not '{WATCH_GROUP}'") + if self.min_api < MIN_API: + reasons.append(f"API {'.'.join(map(str, self.min_api))} is below 5.0.0") + if self.watch_app_memory < MIN_WATCH_APP_MEMORY: + reasons.append(f"watch apps get {self.watch_app_memory // 1024} KB, below 512 KB") + return reasons + + +def parse_version(text: str) -> tuple[int, ...]: + return tuple(int(part) for part in text.split(".")) + + +def load_device(devices: Path, product: str) -> Device: + compiler = json.loads((devices / product / "compiler.json").read_text(encoding="utf-8")) + memory = {app["type"]: int(app["memoryLimit"]) for app in compiler["appTypes"]} + return Device( + product=product, + name=str(compiler.get("displayName", product)), + group=str(compiler.get("webDocDeviceGroup", "")), + min_api=min(parse_version(p["connectIQVersion"]) for p in compiler["partNumbers"]), + watch_app_memory=memory.get("watchApp", 0), + ) + + +def manifest_products(manifest: Path) -> list[str]: + root = ET.parse(manifest).getroot() + return [p.attrib["id"] for p in root.iter(f"{IQ_NS}product")] + + +def check(manifest: Path, devices: Path) -> int: + products = manifest_products(manifest) + worst = EXIT_OK + for product in products: + if not (devices / product / "compiler.json").exists(): + print(f"{product}: no device files (run 'make sdk')") + worst = max(worst, EXIT_MISSING) + continue + device = load_device(devices, product) + for reason in device.problems(): + print(f"{product} ({device.name}): {reason}") + worst = max(worst, EXIT_UNQUALIFIED) + print(f"gen_products: {len(products)} products checked") + return worst + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Check the manifest's products against the target rule.") + parser.add_argument("--check", action="store_true", required=True) + parser.add_argument("--manifest", type=Path, default=Path("manifest.xml")) + parser.add_argument("--devices", type=Path, default=DEFAULT_DEVICES) + args = parser.parse_args(argv) + return check(args.manifest, args.devices) + + +if __name__ == "__main__": + sys.exit(main()) |
