Files
lgtv_audio_cap/tools/build.sh
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

197 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Builds the native service, assembles the package layout and produces the ipk.
#
# ./tools/build.sh # native + stage + package
# ./tools/build.sh native # cross-compile the service only
# ./tools/build.sh package # assemble and run ares-package
# ./tools/build.sh install # ares-install the ipk on the TV
# ./tools/build.sh launch # ares-launch the app
# ./tools/build.sh logs # tail the service log over ssh
# ./tools/build.sh clean
#
# Needs the webOS NDK (arm-webos-linux-gnueabi buildroot SDK) for the native
# part and ares-cli for the packaging part. Point WEBOS_SDK at the SDK if it is
# not in the usual place; set DEVICE to the ares device name (default: tv).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
# `npm install` puts ares-cli here rather than on PATH, so a local install works
# without also needing the global one.
[ -d "$ROOT/node_modules/.bin" ] && PATH="$ROOT/node_modules/.bin:$PATH"
APP_ID=org.webosbrew.audiocap
SERVICE_ID=$APP_ID.service
BINARY=audiocap-service
WEBOS_SDK="${WEBOS_SDK:-$HOME/arm-webos-linux-gnueabi_sdk-buildroot}"
DEVICE="${DEVICE:-tv}"
BUILD_DIR="$ROOT/build"
STAGE_APP="$BUILD_DIR/stage/app"
STAGE_SERVICE="$BUILD_DIR/stage/service"
OUT_DIR="$ROOT/out"
say() { printf '%s\n' "$*"; }
step() { printf '\n== %s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
version() {
python3 - "$ROOT/frontend/appinfo.json" <<'EOF'
import json, sys
print(json.load(open(sys.argv[1]))["version"])
EOF
}
check_sdk() {
local toolchain="$WEBOS_SDK/share/buildroot/toolchainfile.cmake"
if [ ! -f "$toolchain" ]; then
cat >&2 <<EOF
error: no webOS SDK at $WEBOS_SDK
Download and unpack the buildroot NDK, then point WEBOS_SDK at it:
https://github.com/openlgtv/buildroot-nc4/releases
tar xf arm-webos-linux-gnueabi_sdk-buildroot.tar.gz -C \$HOME
\$HOME/arm-webos-linux-gnueabi_sdk-buildroot/relocate-sdk.sh
WEBOS_SDK=\$HOME/arm-webos-linux-gnueabi_sdk-buildroot ./tools/build.sh
The SDK only runs on Linux. On macOS or Windows, compile in a container
instead and then come back here to package:
./tools/docker-build.sh && ./tools/build.sh package
EOF
exit 1
fi
command -v cmake >/dev/null 2>&1 || die "cmake is not installed"
echo "$toolchain"
}
check_ares() {
command -v ares-package >/dev/null 2>&1 || cat >&2 <<'EOF'
error: ares-package is not on PATH
npm install -g @webosose/ares-cli
Then register the TV once (developer mode or the Homebrew Channel's ssh):
ares-setup-device --add tv --info "{'host':'192.168.1.20','port':9922,'username':'root'}"
EOF
command -v ares-package >/dev/null 2>&1
}
build_native() {
local toolchain
toolchain="$(check_sdk)"
step "Cross-compiling the service"
cmake -S native -B "$BUILD_DIR/native" \
-DCMAKE_TOOLCHAIN_FILE="$toolchain" \
-DCMAKE_BUILD_TYPE=Release
cmake --build "$BUILD_DIR/native" --parallel
local binary="$BUILD_DIR/native/$BINARY"
[ -f "$binary" ] || die "the build produced no $BINARY"
say "built $binary"
file "$binary" 2>/dev/null | sed 's/^/ /' || true
}
stage() {
step "Staging the package"
rm -rf "$BUILD_DIR/stage"
mkdir -p "$STAGE_APP" "$STAGE_SERVICE"
cp -R "$ROOT/frontend/." "$STAGE_APP/"
# The mock only exists so the UI can be opened in a desktop browser.
rm -f "$STAGE_APP/js/mock.js"
python3 - "$STAGE_APP/index.html" <<'EOF'
import re, sys
path = sys.argv[1]
html = open(path).read()
html = re.sub(r'\s*<script src="js/mock\.js"></script>', '', html)
open(path, "w").write(html)
EOF
cp "$ROOT/servicefiles/services.json" "$STAGE_SERVICE/"
cp "$ROOT/servicefiles/package.json" "$STAGE_SERVICE/"
cp "$ROOT/servicefiles/audiocapautostart" "$STAGE_SERVICE/"
chmod +x "$STAGE_SERVICE/audiocapautostart"
local binary="$BUILD_DIR/native/$BINARY"
[ -f "$binary" ] || die "no service binary; run './tools/build.sh native' first"
cp "$binary" "$STAGE_SERVICE/$BINARY"
chmod +x "$STAGE_SERVICE/$BINARY"
say "app: $STAGE_APP"
say "service: $STAGE_SERVICE"
}
package() {
check_ares || exit 1
step "Packaging"
mkdir -p "$OUT_DIR"
rm -f "$OUT_DIR"/${APP_ID}_*.ipk
ares-package "$STAGE_APP" "$STAGE_SERVICE" -o "$OUT_DIR"
local ipk
ipk="$(ls -t "$OUT_DIR"/${APP_ID}_*.ipk | head -1)"
say ""
say "$ipk"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$ipk" | sed 's/^/ sha256 /'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$ipk" | sed 's/^/ sha256 /'
fi
}
latest_ipk() {
ls -t "$OUT_DIR"/${APP_ID}_*.ipk 2>/dev/null | head -1
}
install_ipk() {
local ipk
ipk="$(latest_ipk)" || true
[ -n "$ipk" ] || die "no ipk in $OUT_DIR; run './tools/build.sh' first"
step "Installing $ipk on device '$DEVICE'"
ares-install --device "$DEVICE" "$ipk"
say ""
say "The service needs root to reach the TV's audio devices. Either open the"
say "app and press 'Grant root access', or run it here:"
say " ares-shell --device $DEVICE -r \\"
say " '/media/developer/apps/usr/palm/services/org.webosbrew.hbchannel.service/elevate-service $SERVICE_ID'"
}
launch() {
step "Launching on '$DEVICE'"
ares-launch --device "$DEVICE" "$APP_ID"
}
logs() {
step "Service log from '$DEVICE' (ctrl-c to stop)"
# The service keeps its own ring buffer, but journald/pmlog has the crashes.
ares-shell --device "$DEVICE" -r \
"tail -f /var/log/messages 2>/dev/null | grep -i audiocap || journalctl -f | grep -i audiocap"
}
clean() {
step "Cleaning"
rm -rf "$BUILD_DIR" "$OUT_DIR"
say "removed build/ and out/"
}
case "${1:-all}" in
all) build_native; stage; package ;;
native) build_native ;;
stage) stage ;;
package) stage; package ;;
install) install_ipk ;;
launch) launch ;;
logs) logs ;;
clean) clean ;;
version) version ;;
-h|--help)
awk 'NR == 1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$0" ;;
*) die "unknown command '$1' (try --help)" ;;
esac