Put with Files: choose kits, as Manage Kits does, and rebuild kits.scode

A kits step after the zip is chosen, laid out as the Manage Kits panel:
installed kits (what the app needs, greyed), installed but not used, kits
available on the computer, not supported; the Action cell offers Keep at /
Upgrade to / Downgrade to / Add at / REMOVE; Upgrade all kits to latest
version. Kits not in the backup can be added, with their dependencies.

The ticked kits make app.sab's schema (encode_sax takes the kit list, as
Ontrol's PutApp rewrites it). When they are not the device's kits, or
Rebuild kits.scode is ticked, kits.scode is built for them with Ontrol's
sedonac from the Niagara install, after checking the natives the kits call
against the device's platform manifest (PutApp.checkNativeCompatibility).

- kit_build.py: platform manifest lookup, native check, sedonac run
- palette.kit_versions: a checksum released more than once stands for its
  highest version (ontrolPlatform 1.0.1, not 1.0.0)
- palette.unsupported_kits takes the kit list to judge against
- sab_app: a type from a kit the app does not list is a clear error

Tested on a P-ION: a rebuild for its own kits is byte-identical to the one
it runs; adding and then removing the logic kit both come back running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 10:37:08 +03:00
co-authored by Claude Opus 5.5
parent 08b4dec532
commit e070bd0f1e
7 changed files with 776 additions and 119 deletions
+49 -21
View File
@@ -17,6 +17,7 @@ import live_hooks
import manage_kits
import palette
import properties
import put_kits_dialog
import sax_file
import schema
import sox
@@ -781,10 +782,11 @@ def _report_backup(result):
def do_put_with_files():
"""App > Put with Files: puts a Get with Files zip back onto the connected device.
The zip is checked against the device first: the same platform, and an app the
device's kits can run -- the backup's app.sax encoded for them, as Ontrol's tool
does, or its app.sab. The app goes staged, so the device takes it up when
restarted, which is offered once the files are in.
As Ontrol's tool does: the kits the app needs are listed to choose builds for (the
device's by default), the backup's app.sax is encoded for them, and kits.scode is
rebuilt with sedonac when they are not the device's. The zip is checked against the
device on the way. The app goes staged, so the device takes it up when restarted,
which is offered once the files are in.
"""
ds = app_state.device_session
if not app_state.live_mode or ds is None or not ds.is_connected:
@@ -808,19 +810,51 @@ def do_put_with_files():
except Exception as e:
messagebox.showerror("Put with Files", f"Could not read the device:{NL}{NL}{e}")
return
plan = app_backup.plan_put(path, target)
if not plan.ok:
app_state.set_status(f"{os.path.basename(path)} does not match the device", warn=True)
try:
rows = app_backup.kit_rows(path, target)
except Exception as e:
messagebox.showerror("Put with Files", f"{os.path.basename(path)}:{NL}{NL}{e}")
return
backup_platform = app_backup.platform_of_kits(r.name for r in rows if r.backup or r.used)
if backup_platform is None or backup_platform is not target.platform:
messagebox.showerror(
"Put with Files",
f"{os.path.basename(path)} cannot be put on this device:{NL}{NL}" +
NL.join(plan.problems))
f"{os.path.basename(path)} cannot be put on this device:{NL}{NL}"
f"The backup is from {app_backup.platform_label(backup_platform)}; the device "
f"is {app_backup.platform_label(target.platform)}.")
return
if not messagebox.askokcancel("Put with Files", _describe_put(plan),
icon=messagebox.WARNING, default=messagebox.CANCEL):
rebuild = put_kits_dialog.ask(app_state.window, rows)
if rebuild is None:
return
def finish(_result, error):
def planned(plan, error):
if error is not None:
messagebox.showerror("Put with Files", f"Preparing the put failed:{NL}{NL}{error}")
return
if not plan.ok:
app_state.set_status(f"{os.path.basename(path)} cannot be put on the device",
warn=True)
messagebox.showerror(
"Put with Files",
f"{os.path.basename(path)} cannot be put on this device:{NL}{NL}" +
NL.join(plan.problems))
return
if messagebox.askokcancel("Put with Files", _describe_put(plan),
icon=messagebox.WARNING, default=messagebox.CANCEL):
_put(plan)
def prepare(status):
status("Preparing the app" + (" and building kits.scode" if rebuild or any(
map(app_backup.kit_change, rows)) else "") + " ...", None)
return app_backup.plan_put(path, target, rows, rebuild)
def _put(plan):
_run_with_progress("Put with Files",
lambda status: app_backup.put_with_files(ds.sox_client, plan,
target, status),
lambda result, error: finish(plan, result, error))
def finish(plan, _result, error):
if error is not None:
app_state.set_status(f"Put with Files failed: {error}", warn=True)
messagebox.showerror(
@@ -839,10 +873,7 @@ def do_put_with_files():
if _invoke_app_action("restart"):
app_state.set_status(f"Put {os.path.basename(path)}; the device is restarting")
_run_with_progress("Put with Files",
lambda status: app_backup.put_with_files(ds.sox_client, plan, target,
status),
finish)
_run_with_progress("Put with Files", prepare, planned)
def _describe_put(plan):
@@ -854,12 +885,9 @@ def _describe_put(plan):
(" Its fonts and images are deleted and replaced by the backup's."
if plan.has_screen else ""), "",
f" app.sab, from {plan.app_source}"]
lines += [f" {line}" for line in plan.reencoded]
if plan.kit_changes:
lines += [" kits.scode, since the device's kits cannot load the app:"]
if plan.scode_source:
lines.append(f" kits.scode, {plan.scode_source}")
lines += [f" {line}" for line in plan.kit_changes]
lines += [" The device's firmware must support these kits, or the app will not "
"start."]
if not plan.has_screen:
return NL.join(lines + ([""] + plan.warnings if plan.warnings else []))
lines.append(f" {images} images" +