Files
lgtv_audio_cap/tools/make-manifest.py
T
Rene KievitsandClaude Opus 5 3e4d0e17bc Build the service in a container, and fix what the target compiler found
The openlgtv NDK is a Linux toolchain with no macOS or Windows build, so
tools/build.sh could not produce a binary anywhere else. docker-build.sh
bakes the SDK into an image and compiles there; packaging and deploy stay
on the host, where the TV is reachable. The SDK ships aarch64 as well as
x86_64, so the image picks the one matching the daemon and Apple Silicon
builds natively rather than under emulation.

Cross-compiling for real turned up three things the host compiler did
not:

  sink_hyperhdr_viz.c  read p->width and p->height to format the error
                       message after free(p)
  sink_hyperhdr.c      an SDP connection line of 128 bytes cannot hold
                       "IN IP4 " plus a 127-byte host plus "/255", so a
                       long hostname would silently lose its TTL suffix
  common/log.c         the log body was sized to the whole ring line,
                       leaving nothing for the prefix; budget for it so
                       the bound is provable rather than left to
                       snprintf

A clean cross-compile is now warning-free, and readelf confirms the
design rule holds: luna-service2, glib, PmLogLib and libc, with no
libpulse or libasound.

Also: @webosose/ares-cli was pinned to ^3.0.0, which does not exist
(latest is 2.4.0), so npm install failed outright. build.sh now puts
node_modules/.bin on PATH so a local install is enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 10:29:09 +02:00

93 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Writes the Homebrew Channel manifest for a built ipk.
The Homebrew Channel installs apps from a manifest that points at the ipk and
carries its hash. Publishing means putting this file somewhere stable (a GitHub
release asset, or the repository itself) and submitting the URL to
webosbrew/repo.
python3 tools/make-manifest.py \\
--base-url https://github.com/you/lgtv-audio-cap/releases/download/v1.0.0
Defaults to the newest ipk in out/ and writes out/manifest.json.
"""
import argparse
import glob
import hashlib
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.join(HERE, os.pardir)
SOURCE_URL = "https://git.crylia.de/Crylia/lgtv_audio_cap"
def newest_ipk(directory):
matches = sorted(glob.glob(os.path.join(directory, "*.ipk")),
key=os.path.getmtime, reverse=True)
return matches[0] if matches else None
def sha256(path):
digest = hashlib.sha256()
with open(path, "rb") as fh:
for block in iter(lambda: fh.read(1 << 20), b""):
digest.update(block)
return digest.hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--ipk", default=None, help="path to the ipk")
parser.add_argument("--base-url", default=None,
help="URL the ipk and icon will be served from")
parser.add_argument("--source-url", default=SOURCE_URL,
help="project page shown in the Homebrew Channel")
parser.add_argument("--out", default=os.path.join(ROOT, "out", "manifest.json"))
args = parser.parse_args()
appinfo = json.load(open(os.path.join(ROOT, "frontend", "appinfo.json")))
ipk = args.ipk or newest_ipk(os.path.join(ROOT, "out"))
if not ipk or not os.path.exists(ipk):
print("no ipk found; run ./tools/build.sh first", file=sys.stderr)
return 1
name = os.path.basename(ipk)
base = (args.base_url or "").rstrip("/")
manifest = {
"id": appinfo["id"],
"version": appinfo["version"],
"type": appinfo.get("type", "web"),
"title": appinfo.get("title", appinfo["id"]),
"appDescription": appinfo.get("appDescription", ""),
"iconUri": base + "/icon.png" if base else "icon.png",
"sourceUrl": args.source_url,
# The service reads the TV's audio devices, which are root-only. The
# Homebrew Channel uses this to elevate the service at install time.
"rootRequired": True,
"ipkUrl": base + "/" + name if base else name,
"ipkHash": {"sha256": sha256(ipk)},
"ipkSize": os.path.getsize(ipk),
}
os.makedirs(os.path.dirname(args.out), exist_ok=True)
with open(args.out, "w") as fh:
json.dump(manifest, fh, indent=2)
fh.write("\n")
print(json.dumps(manifest, indent=2))
print("\nwrote %s" % os.path.relpath(args.out, ROOT), file=sys.stderr)
if not base:
print("note: no --base-url given, so ipkUrl and iconUri are relative",
file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())