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>
93 lines
3.1 KiB
Python
Executable File
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())
|