Manifest format update (#33)

This commit is contained in:
Ken Van Hoeylandt
2026-07-03 00:04:29 +02:00
committed by GitHub
parent 8a0f9ef4e4
commit 4ab2377970
19 changed files with 179 additions and 249 deletions
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.brainfuck
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.4.0
[app] app.version.code=4
id=one.tactility.brainfuck app.name=Brainfuck interpreter
versionName=0.3.0 app.description=Brainfuck esoteric language interpreter
versionCode=3
name=Brainfuck interpreter
description=Brainfuck esoteric language interpreter
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.breakout
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.4.0
[app] app.version.code=4
id=one.tactility.breakout app.name=Breakout
versionName=0.3.0 app.description=Classic brick-breaking arcade game
versionCode=3
name=Breakout
description=Classic brick-breaking arcade game
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.calculator
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.5.0
[app] app.version.code=5
id=one.tactility.calculator app.name=Calculator
versionName=0.4.0
versionCode=4
name=Calculator
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.diceware
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.5.0
[app] app.version.code=5
id=one.tactility.diceware app.name=Diceware
versionName=0.4.0
versionCode=4
name=Diceware
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32s3,esp32p4
sdk=0.7.0 app.id=one.tactility.epubreader
platforms=esp32s3,esp32p4 app.version.name=0.3.0
[app] app.version.code=3
id=one.tactility.epubreader app.name=Epub Reader
versionName=0.2.0 app.description=Epub and text file reader. Requires PSRAM!
versionCode=2
name=Epub Reader
description=Epub and text file reader. Requires PSRAM!
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.gpio
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.6.0
[app] app.version.code=6
id=one.tactility.gpio app.name=GPIO
versionName=0.5.0
versionCode=5
name=GPIO
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.graphicsdemo
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.5.0
[app] app.version.code=5
id=one.tactility.graphicsdemo app.name=Graphics Demo
versionName=0.4.0
versionCode=4
name=Graphics Demo
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.helloworld
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.5.0
[app] app.version.code=5
id=one.tactility.helloworld app.name=Hello World
versionName=0.4.0
versionCode=4
name=Hello World
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32s3,esp32p4
sdk=0.7.0 app.id=one.tactility.m5unittest
platforms=esp32s3,esp32p4 app.version.name=0.3.0
[app] app.version.code=3
id=one.tactility.m5unittest app.name=M5 Unit Test
versionName=0.2.0
versionCode=2
name=M5 Unit Test
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.magic8ball
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.4.0
[app] app.version.code=4
id=one.tactility.magic8ball app.name=Magic 8-Ball
versionName=0.3.0
versionCode=3
name=Magic 8-Ball
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32s3,esp32p4
sdk=0.7.0 app.id=one.tactility.mediakeys
platforms=esp32s3,esp32p4 app.version.name=0.3.0
[app] app.version.code=3
id=one.tactility.mediakeys app.name=Media Keys
versionName=0.2.0 app.description=Bluetooth media keys. Touch or Physical Keyboard control\nB - previous, P - play/pause, N - next, M - mute, D - volume down, U - volume up.\nQ or ESC to exit focus.
versionCode=2
name=Media Keys
description=Bluetooth media keys. Touch or Physical Keyboard control\nB - previous, P - play/pause, N - next, M - mute, D - volume down, U - volume up.\nQ or ESC to exit focus.
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.mystifydemo
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.5.0
[app] app.version.code=5
id=one.tactility.mystifydemo app.name=Mystify Demo
versionName=0.4.0
versionCode=4
name=Mystify Demo
+7 -10
View File
@@ -1,10 +1,7 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.serialconsole
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.6.0
[app] app.version.code=6
id=one.tactility.serialconsole app.name=Serial Console
versionName=0.5.0
versionCode=5
name=Serial Console
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.snake
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.7.0
[app] app.version.code=7
id=one.tactility.snake app.name=Snake
versionName=0.6.0 app.description=Classic Snake game
versionCode=6
name=Snake
description=Classic Snake game
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.tamatac
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.3.0
[app] app.version.code=3
id=one.tactility.tamatac app.name=TamaTac
versionName=0.2.0 app.description=Virtual pet inspired by Tamagotchi. Only runs on devices with PSRAM.
versionCode=2
name=TamaTac
description=Virtual pet inspired by Tamagotchi. Only runs on devices with PSRAM.
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.todolist
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.4.0
[app] app.version.code=4
id=one.tactility.todolist app.name=Todo List
versionName=0.3.0 app.description=Simple task list manager
versionCode=3
name=Todo List
description=Simple task list manager
+8 -11
View File
@@ -1,11 +1,8 @@
[manifest] manifest.version=0.2
version=0.1 target.sdk=0.7.0
[target] target.platforms=esp32,esp32s3,esp32c6,esp32p4
sdk=0.7.0 app.id=one.tactility.twoeleven
platforms=esp32,esp32s3,esp32c6,esp32p4 app.version.name=0.6.0
[app] app.version.code=6
id=one.tactility.twoeleven app.name=2048
versionName=0.5.0 app.description=A fun, customizable 2048 sliding tile game for tactility!\nSlide tiles to combine numbers and reach 2048.\nChoose grid sizes: 3x3 (easy), 4x4 (classic), 5x5, or 6x6 (expert).
versionCode=5
name=2048
description=A fun, customizable 2048 sliding tile game for tactility!\nSlide tiles to combine numbers and reach 2048.\nChoose grid sizes: 3x3 (easy), 4x4 (classic), 5x5, or 6x6 (expert).
+30 -36
View File
@@ -2,13 +2,20 @@ import json
import tarfile import tarfile
import os import os
import tempfile import tempfile
import configparser
import sys import sys
def read_properties_file(path): def read_properties_file(path):
config = configparser.RawConfigParser() properties = {}
config.read(path) with open(path, "r") as file:
return config for line in file:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
key, sep, value = line.partition("=")
if not sep:
continue
properties[key.strip()] = value.strip()
return properties
def get_manifest(appPath): def get_manifest(appPath):
"""Extract only the file named 'manifest.properties' from the given tar/tar.gz """Extract only the file named 'manifest.properties' from the given tar/tar.gz
@@ -62,12 +69,12 @@ def get_manifest(appPath):
return None return None
def get_versioned_file_name(manifest): def get_versioned_file_name(manifest):
app_id = manifest["app"]["id"] app_id = manifest["app.id"]
version_code = manifest["app"]["versionCode"] version_code = manifest["app.version.code"]
return f"{app_id}-{version_code}.app" return f"{app_id}-{version_code}.app"
def get_os_version(manifest): def get_os_version(manifest):
sdk = manifest["target"]["sdk"] sdk = manifest["target.sdk"]
# Remove trailing hyphen suffix if present # Remove trailing hyphen suffix if present
if "-" in sdk: if "-" in sdk:
return sdk.rsplit("-", 1)[0].strip() return sdk.rsplit("-", 1)[0].strip()
@@ -75,38 +82,25 @@ def get_os_version(manifest):
return sdk return sdk
def manifest_config_to_flat_json(manifest): def manifest_config_to_flat_json(manifest):
"""Convert a ConfigParser manifest into a flat JSON-like dict. """Convert a flat (V2) manifest dict into a flat JSON-like dict.
Expected sections/keys (case-insensitive for keys): Expected keys:
- [app] app.id -> appId
id -> appId app.version.name -> appVersionName
versionName -> appVersionName app.version.code -> appVersionCode (int)
versionCode -> appVersionCode (int) app.name -> appName
name -> appName app.description -> appDescription (optional; default "")
description -> appDescription (optional; default "") target.sdk -> targetSdk
- [target] target.platforms -> targetPlatforms (comma-separated list)
sdk -> targetSdk
platforms -> targetPlatforms (comma-separated list)
Unknown/missing values fall back to sensible defaults per requirements. Unknown/missing values fall back to sensible defaults per requirements.
""" """
def get_opt(section, option, default=None):
if not manifest.has_section(section):
return default
# try exact option then lowercase (RawConfigParser lowercases by default)
if manifest.has_option(section, option):
return manifest.get(section, option)
low = option.lower()
if manifest.has_option(section, low):
return manifest.get(section, low)
return default
# Map values # Map values
app_id = get_opt("app", "id", "") app_id = manifest.get("app.id", "")
app_version_name = get_opt("app", "versionName", "") app_version_name = manifest.get("app.version.name", "")
app_version_code_raw = get_opt("app", "versionCode", "0") app_version_code_raw = manifest.get("app.version.code", "0")
app_name = get_opt("app", "name", "") app_name = manifest.get("app.name", "")
app_description = get_opt("app", "description", "") or "" app_description = manifest.get("app.description", "") or ""
# Coerce version code to int safely # Coerce version code to int safely
try: try:
@@ -114,8 +108,8 @@ def manifest_config_to_flat_json(manifest):
except Exception: except Exception:
app_version_code = 0 app_version_code = 0
target_sdk = get_opt("target", "sdk", "") target_sdk = manifest.get("target.sdk", "")
platforms_raw = get_opt("target", "platforms", "") platforms_raw = manifest.get("target.platforms", "")
target_platforms = [p.strip() for p in str(platforms_raw).split(",") if p.strip()] if platforms_raw is not None else [] target_platforms = [p.strip() for p in str(platforms_raw).split(",") if p.strip()] if platforms_raw is not None else []
filename = get_versioned_file_name(manifest) filename = get_versioned_file_name(manifest)
+22 -35
View File
@@ -1,4 +1,3 @@
import configparser
import json import json
import os import os
import re import re
@@ -13,7 +12,7 @@ import tarfile
from urllib.parse import urlparse from urllib.parse import urlparse
ttbuild_path = ".tactility" ttbuild_path = ".tactility"
ttbuild_version = "3.5.1" ttbuild_version = "4.0.0"
ttbuild_cdn = "https://cdn.tactilityproject.org" ttbuild_cdn = "https://cdn.tactilityproject.org"
ttbuild_sdk_json_validity = 3600 # seconds ttbuild_sdk_json_validity = 3600 # seconds
ttport = 6666 ttport = 6666
@@ -106,9 +105,17 @@ def get_url(ip, path):
return f"http://{ip}:{ttport}{path}" return f"http://{ip}:{ttport}{path}"
def read_properties_file(path): def read_properties_file(path):
config = configparser.RawConfigParser() properties = {}
config.read(path) with open(path, "r") as file:
return config for line in file:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
key, sep, value = line.partition("=")
if not sep:
continue
properties[key.strip()] = value.strip()
return properties
#endregion Core #endregion Core
@@ -231,32 +238,12 @@ def read_manifest():
return read_properties_file("manifest.properties") return read_properties_file("manifest.properties")
def validate_manifest(manifest): def validate_manifest(manifest):
# [manifest] for key in ("manifest.version", "target.sdk", "target.platforms", "app.id", "app.version.name", "app.version.code", "app.name"):
if not "manifest" in manifest: if key not in manifest:
exit_with_error("Invalid manifest format: [manifest] not found") exit_with_error(f"Invalid manifest format: {key} not found")
if not "version" in manifest["manifest"]:
exit_with_error("Invalid manifest format: [manifest] version not found")
# [target]
if not "target" in manifest:
exit_with_error("Invalid manifest format: [target] not found")
if not "sdk" in manifest["target"]:
exit_with_error("Invalid manifest format: [target] sdk not found")
if not "platforms" in manifest["target"]:
exit_with_error("Invalid manifest format: [target] platforms not found")
# [app]
if not "app" in manifest:
exit_with_error("Invalid manifest format: [app] not found")
if not "id" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] id not found")
if not "versionName" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] versionName not found")
if not "versionCode" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] versionCode not found")
if not "name" in manifest["app"]:
exit_with_error("Invalid manifest format: [app] name not found")
def is_valid_manifest_platform(manifest, platform): def is_valid_manifest_platform(manifest, platform):
manifest_platforms = manifest["target"]["platforms"].split(",") manifest_platforms = manifest["target.platforms"].split(",")
return platform in manifest_platforms return platform in manifest_platforms
def validate_manifest_platform(manifest, platform): def validate_manifest_platform(manifest, platform):
@@ -265,7 +252,7 @@ def validate_manifest_platform(manifest, platform):
def get_manifest_target_platforms(manifest, requested_platform): def get_manifest_target_platforms(manifest, requested_platform):
if requested_platform == "" or requested_platform is None: if requested_platform == "" or requested_platform is None:
return manifest["target"]["platforms"].split(",") return manifest["target.platforms"].split(",")
else: else:
validate_manifest_platform(manifest, requested_platform) validate_manifest_platform(manifest, requested_platform)
return [requested_platform] return [requested_platform]
@@ -512,7 +499,7 @@ def build_action(manifest, platform_arg, skip_build):
if use_local_sdk: if use_local_sdk:
global local_base_path global local_base_path
local_base_path = os.environ.get("TACTILITY_SDK_PATH") local_base_path = os.environ.get("TACTILITY_SDK_PATH")
validate_local_sdks(platforms_to_build, manifest["target"]["sdk"]) validate_local_sdks(platforms_to_build, manifest["target.sdk"])
if should_fetch_sdkconfig_files(platforms_to_build): if should_fetch_sdkconfig_files(platforms_to_build):
fetch_sdkconfig_files(platforms_to_build) fetch_sdkconfig_files(platforms_to_build)
@@ -521,7 +508,7 @@ def build_action(manifest, platform_arg, skip_build):
sdk_json = read_sdk_json() sdk_json = read_sdk_json()
validate_self(sdk_json) validate_self(sdk_json)
# Build # Build
sdk_version = manifest["target"]["sdk"] sdk_version = manifest["target.sdk"]
if not use_local_sdk: if not use_local_sdk:
if not sdk_download_all(sdk_version, platforms_to_build): if not sdk_download_all(sdk_version, platforms_to_build):
exit_with_error("Failed to download one or more SDKs") exit_with_error("Failed to download one or more SDKs")
@@ -570,7 +557,7 @@ def get_device_info(ip):
print_status_error(f"Device info request failed: {e}") print_status_error(f"Device info request failed: {e}")
def run_action(manifest, ip): def run_action(manifest, ip):
app_id = manifest["app"]["id"] app_id = manifest["app.id"]
print_status_busy("Running") print_status_busy("Running")
url = get_url(ip, "/app/run") url = get_url(ip, "/app/run")
params = {'id': app_id} params = {'id': app_id}
@@ -614,7 +601,7 @@ def install_action(ip, platforms):
return False return False
def uninstall_action(manifest, ip): def uninstall_action(manifest, ip):
app_id = manifest["app"]["id"] app_id = manifest["app.id"]
print_status_busy("Uninstalling") print_status_busy("Uninstalling")
url = get_url(ip, "/app/uninstall") url = get_url(ip, "/app/uninstall")
params = {'id': app_id} params = {'id': app_id}
@@ -670,7 +657,7 @@ if __name__ == "__main__":
exit_with_error("manifest.properties not found") exit_with_error("manifest.properties not found")
manifest = read_manifest() manifest = read_manifest()
validate_manifest(manifest) validate_manifest(manifest)
all_platform_targets = manifest["target"]["platforms"].split(",") all_platform_targets = manifest["target.platforms"].split(",")
# Update SDK cache (tool.json) # Update SDK cache (tool.json)
if not use_local_sdk and should_update_tool_json() and not update_tool_json(): if not use_local_sdk and should_update_tool_json() and not update_tool_json():
exit_with_error("Failed to retrieve SDK info") exit_with_error("Failed to retrieve SDK info")