Kit Manager: detect unsupported kits via platform database
Scan sedona/platforms/db/ to find which native kits are compiled for the SAX's platform. Kits with native code for other platforms, kits providing a PlatformService for a different device, and their transitive dependents are placed in a "Not supported" section with a red-cross checkbox that cannot be toggled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
|
||||
import configparser
|
||||
import os
|
||||
|
||||
VERSION = "0.0.0.071"
|
||||
VERSION = "0.0.0.072"
|
||||
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||
DEFAULT_GRID_SIZE = 10
|
||||
DEFAULT_ADD_TIMESTAMP = True
|
||||
|
||||
+25
-5
@@ -37,8 +37,14 @@ TICK_BLOCKS = [
|
||||
(2, 5), (3, 6), (4, 7), (5, 6), (6, 5), (7, 4), (8, 3), (9, 2),
|
||||
]
|
||||
|
||||
# The cross (X), as filled 2x2 blocks: two diagonal strokes.
|
||||
CROSS_BLOCKS = [
|
||||
(2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9),
|
||||
(9, 2), (8, 3), (7, 4), (6, 5), (5, 6), (4, 7), (3, 8), (2, 9),
|
||||
]
|
||||
|
||||
def make_checkbox_image(checked, enabled):
|
||||
|
||||
def make_checkbox_image(checked, enabled, cross=False):
|
||||
"""A checkbox drawn pixel by pixel, so a locked tick can be greyed out.
|
||||
|
||||
Tk has no disabled state for a cell, and a row tag would grey the whole row including
|
||||
@@ -56,7 +62,10 @@ def make_checkbox_image(checked, enabled):
|
||||
image.put(border, to=(0, 0, 1, CHECKBOX_SIZE))
|
||||
image.put(border, to=(CHECKBOX_SIZE - 1, 0, CHECKBOX_SIZE, CHECKBOX_SIZE))
|
||||
|
||||
if checked:
|
||||
if cross:
|
||||
for x, y in CROSS_BLOCKS:
|
||||
image.put("#b04040", to=(x, y, x + 2, y + 2))
|
||||
elif checked:
|
||||
for x, y in TICK_BLOCKS:
|
||||
image.put(ink, to=(x, y, x + 2, y + 2))
|
||||
|
||||
@@ -134,6 +143,10 @@ def collect_rows():
|
||||
# clearest place to see that the file points at something missing.
|
||||
others = [name for name in installed if name not in declared_names]
|
||||
|
||||
bad = palette.unsupported_kits()
|
||||
compatible = [name for name in others if name not in bad]
|
||||
incompatible = [name for name in others if name in bad]
|
||||
|
||||
groups = [
|
||||
("Installed kits", [
|
||||
(name, checksum) for name, checksum in declared if name in required
|
||||
@@ -141,7 +154,8 @@ def collect_rows():
|
||||
("Installed but not used kits", [
|
||||
(name, checksum) for name, checksum in declared if name not in required
|
||||
]),
|
||||
("Kits available on the computer", [(name, None) for name in others]),
|
||||
("Kits available on the computer", [(name, None) for name in compatible]),
|
||||
("Not supported", [(name, None) for name in incompatible]),
|
||||
]
|
||||
|
||||
rows = []
|
||||
@@ -153,6 +167,7 @@ def collect_rows():
|
||||
builds = palette.kit_builds(name)
|
||||
latest = builds[0] if builds else None
|
||||
is_declared = name in declared_names
|
||||
is_unsupported = name in bad
|
||||
options = action_options(name, checksum, is_declared, builds)
|
||||
|
||||
if not is_declared:
|
||||
@@ -168,6 +183,7 @@ def collect_rows():
|
||||
"kit": name,
|
||||
"declared": is_declared,
|
||||
"required": name in required,
|
||||
"unsupported": is_unsupported,
|
||||
"checksum": checksum,
|
||||
"builds": builds,
|
||||
"latest": latest,
|
||||
@@ -175,7 +191,6 @@ def collect_rows():
|
||||
"latest_text": column_label(*latest) if latest else NOT_DECLARED,
|
||||
"options": options,
|
||||
"choice": 0,
|
||||
# The tick reflects the file as it stands: declared kits start ticked.
|
||||
"checked": is_declared,
|
||||
})
|
||||
return rows
|
||||
@@ -248,6 +263,7 @@ class KitsPanel:
|
||||
(True, False): make_checkbox_image(True, False),
|
||||
(False, False): make_checkbox_image(False, False),
|
||||
}
|
||||
self.cross_box = make_checkbox_image(False, False, cross=True)
|
||||
|
||||
hint = ttk.Label(
|
||||
self.frame,
|
||||
@@ -370,6 +386,8 @@ class KitsPanel:
|
||||
return (row["kit"], row["installed_text"], row["latest_text"], self._action_text(row))
|
||||
|
||||
def _row_image(self, row):
|
||||
if row.get("unsupported"):
|
||||
return self.cross_box
|
||||
return self.boxes[(row["checked"], not row["required"])]
|
||||
|
||||
def _rebuild_table(self):
|
||||
@@ -403,7 +421,7 @@ class KitsPanel:
|
||||
|
||||
def _toggle_check(self, item):
|
||||
row = self.row_by_item[item]
|
||||
if row["required"]:
|
||||
if row["required"] or row.get("unsupported"):
|
||||
return
|
||||
|
||||
row["checked"] = not row["checked"]
|
||||
@@ -420,6 +438,8 @@ class KitsPanel:
|
||||
|
||||
def _edit_action(self, item):
|
||||
row = self.row_by_item[item]
|
||||
if row.get("unsupported"):
|
||||
return
|
||||
cell = self.table.bbox(item, "action")
|
||||
if not cell:
|
||||
return
|
||||
|
||||
+153
@@ -246,9 +246,162 @@ def list_component_types(kit_name):
|
||||
return sorted(entries)
|
||||
|
||||
|
||||
_unsupported_cache = {} # frozenset(sax kit names) -> set of unsupported kit names
|
||||
|
||||
|
||||
def _platforms_db():
|
||||
return os.path.join(app_state.SEDONA_HOME, "platforms", "db")
|
||||
|
||||
|
||||
def _platform_native_kits():
|
||||
"""Return (matching, all_platforms) — two sets of kit names.
|
||||
|
||||
*matching* is the union of nativeKit names from platform builds that include
|
||||
every PlatformService-providing kit the SAX declares. *all_platforms* is the
|
||||
union across every build in the database regardless of match.
|
||||
|
||||
A kit that appears in *all_platforms* but not in *matching* has native code
|
||||
compiled for a different device and cannot work with this SAX.
|
||||
"""
|
||||
sax_names = set(app_state.active_schema_kits.keys())
|
||||
db = _platforms_db()
|
||||
if not os.path.isdir(db):
|
||||
return None, None
|
||||
|
||||
sax_platform_kits = set()
|
||||
for name in sax_names:
|
||||
kit_dir = os.path.join(_manifests_dir(), name)
|
||||
if not os.path.isdir(kit_dir):
|
||||
continue
|
||||
for entry in os.listdir(kit_dir):
|
||||
if not (entry.startswith(f"{name}-") and entry.endswith(".xml")):
|
||||
continue
|
||||
try:
|
||||
tree = ET.parse(os.path.join(kit_dir, entry))
|
||||
except ET.ParseError:
|
||||
continue
|
||||
for t in tree.getroot().findall(".//type"):
|
||||
text = t.get("name", "") + " " + t.get("base", "")
|
||||
if "PlatformService" in text:
|
||||
sax_platform_kits.add(name)
|
||||
break
|
||||
|
||||
if not sax_platform_kits:
|
||||
return None, None
|
||||
|
||||
matching = set()
|
||||
all_platforms = set()
|
||||
found = False
|
||||
for vendor in os.listdir(db):
|
||||
vd = os.path.join(db, vendor)
|
||||
if not os.path.isdir(vd):
|
||||
continue
|
||||
for device in os.listdir(vd):
|
||||
dd = os.path.join(vd, device)
|
||||
if not os.path.isdir(dd):
|
||||
continue
|
||||
for build in os.listdir(dd):
|
||||
mf = os.path.join(dd, build, ".par", "platformManifest.xml")
|
||||
if not os.path.isfile(mf):
|
||||
continue
|
||||
try:
|
||||
tree = ET.parse(mf)
|
||||
except ET.ParseError:
|
||||
continue
|
||||
nk_names = set()
|
||||
for nk in tree.getroot().findall(".//nativeKit"):
|
||||
nk_names.add(nk.get("depend", "").split()[0])
|
||||
all_platforms.update(nk_names)
|
||||
if sax_platform_kits <= nk_names:
|
||||
matching.update(nk_names)
|
||||
found = True
|
||||
|
||||
return (matching if found else None), all_platforms
|
||||
|
||||
|
||||
def unsupported_kits():
|
||||
"""Kit names that cannot work with the open file's platform.
|
||||
|
||||
A kit is unsupported when:
|
||||
- it has native code (hasNatives or listed as nativeKit on some platform)
|
||||
but is not compiled into any build matching the SAX's device, or
|
||||
- it provides a PlatformService type for a different device.
|
||||
|
||||
Kits that depend on an unsupported kit are also flagged.
|
||||
"""
|
||||
sax_names = frozenset(app_state.active_schema_kits.keys())
|
||||
if sax_names in _unsupported_cache:
|
||||
return _unsupported_cache[sax_names]
|
||||
|
||||
matching_natives, all_natives = _platform_native_kits()
|
||||
if matching_natives is None:
|
||||
_unsupported_cache[sax_names] = set()
|
||||
return set()
|
||||
|
||||
unsupported = set()
|
||||
all_kits = list_kits()
|
||||
|
||||
for name in all_kits:
|
||||
if name in sax_names:
|
||||
continue
|
||||
|
||||
# Native on another platform but not on the matching one.
|
||||
if name in all_natives and name not in matching_natives:
|
||||
unsupported.add(name)
|
||||
continue
|
||||
|
||||
kit_dir = os.path.join(_manifests_dir(), name)
|
||||
if not os.path.isdir(kit_dir):
|
||||
continue
|
||||
for entry in os.listdir(kit_dir):
|
||||
if not (entry.startswith(f"{name}-") and entry.endswith(".xml")):
|
||||
continue
|
||||
try:
|
||||
tree = ET.parse(os.path.join(kit_dir, entry))
|
||||
except ET.ParseError:
|
||||
continue
|
||||
root = tree.getroot()
|
||||
has_natives = root.get("hasNatives") == "true"
|
||||
has_platform_type = any(
|
||||
"PlatformService" in (t.get("name", "") + " " + t.get("base", ""))
|
||||
for t in root.findall(".//type")
|
||||
)
|
||||
if (has_natives or has_platform_type) and name not in matching_natives:
|
||||
unsupported.add(name)
|
||||
break
|
||||
|
||||
# Transitive: kits that depend on an unsupported kit
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for name in all_kits:
|
||||
if name in unsupported or name in sax_names:
|
||||
continue
|
||||
kit_dir = os.path.join(_manifests_dir(), name)
|
||||
if not os.path.isdir(kit_dir):
|
||||
continue
|
||||
for entry in os.listdir(kit_dir):
|
||||
if not (entry.startswith(f"{name}-") and entry.endswith(".xml")):
|
||||
continue
|
||||
try:
|
||||
tree = ET.parse(os.path.join(kit_dir, entry))
|
||||
except ET.ParseError:
|
||||
continue
|
||||
for dep in tree.getroot().findall(".//depend"):
|
||||
dep_name = dep.get("on", "").split()[0]
|
||||
if dep_name in unsupported:
|
||||
unsupported.add(name)
|
||||
changed = True
|
||||
break
|
||||
|
||||
_unsupported_cache[sax_names] = unsupported
|
||||
return unsupported
|
||||
|
||||
|
||||
def invalidate():
|
||||
"""Clears every cache, for when Sedona home changes or a kit is repinned."""
|
||||
_versions_cache.clear()
|
||||
_manifest_cache.clear()
|
||||
_component_cache.clear()
|
||||
_builds_cache.clear()
|
||||
_unsupported_cache.clear()
|
||||
|
||||
Reference in New Issue
Block a user