Skip to content

Commit 3bcf68b

Browse files
committed
fix(tvos): sign simulator consumers with real Keychain entitlements
1 parent 3f8207d commit 3bcf68b

2 files changed

Lines changed: 33 additions & 62 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,60 +11,6 @@
1111
10-foot / remote navigation: build around GridView / ListView + FocusScope + KeyNavigation + Keys. KeyNavigation is specifically for arrow/tab-based focus jumps, and FocusScope exists to keep reusable focus regions sane, which is exactly the problem space for D-pad TV UIs.
1212
HTTP asset caching: QNetworkDiskCache for posters, backdrops, and image responses. It is basic, but it plugs directly into QNetworkAccessManager; just remember it is basic by design and defaults to a 50 MB limit, so you will probably want to raise that.
1313

14-
## Apple TV review branch
15-
16-
`feature/tvos` contains the Apple TV target independently of the iOS target.
17-
Qt does **not** list tvOS among its officially supported/tested platforms.
18-
The branch source-builds the pinned Qt UIKit port for `CMAKE_SYSTEM_NAME=tvOS`;
19-
an iOS binary kit or macOS Qt library cannot be substituted. See the upstream
20-
[platform matrix](https://doc.qt.io/qt-6/supported-platforms.html) and
21-
[UIKit platform source](https://github.com/qt/qtbase/tree/6.11/src/plugins/platforms/ios).
22-
23-
**Validation status:** this is not yet a working or shippable tvOS build.
24-
The native Xcode CI attempt currently fails while compiling Qt's UIKit
25-
platform plugin because it references iOS-only orientation APIs. QtCore,
26-
QtGui, QtNetwork, and QtOpenGL compilation alone is not application/playback
27-
proof. There is no successful Apple TV application link or simulator/device
28-
smoke result, and this target must not be included in release support claims.
29-
30-
On macOS with Xcode's Apple TV SDK installed:
31-
32-
```sh
33-
brew install cmake ninja meson pkg-config bash python imagemagick
34-
# Install matching pinned macOS host Qt tools (Qt Online Installer or aqt).
35-
export QT_HOST_PATH=/absolute/path/to/Qt/macos
36-
APPLE_SDK=appletvsimulator APPLE_ARCH=arm64 bash tools/build-tvos.sh
37-
bash tools/apple/smoke-tvos.sh \
38-
build/tvos/appletvsimulator-arm64/install/Spool.app
39-
```
40-
41-
The build verifies source digests, builds Qt and the static mpv/media closure
42-
for the selected SDK, generates the Apple TV layered icon/Top Shelf artwork,
43-
and packages `Spool.app`. Set `APPLE_SDK=appletvos`, `APPLE_ARCH=arm64`,
44-
`CODE_SIGNING_ALLOWED=YES`, and `APPLE_DEVELOPMENT_TEAM` for a signed device
45-
build. Archive/export using Xcode with your provisioning profile; a simulator
46-
bundle is not an App Store artifact. The dedicated `tvOS source build`
47-
workflow builds and runs the existing UI and video-render consumer smoke tests
48-
in an isolated Apple TV simulator. Simulator success does not establish
49-
physical-device decoder/audio performance, Siri Remote gesture feel, VoiceOver,
50-
HDR/frame-rate matching, signing, or App Store acceptance.
51-
52-
The target reuses TV focus, hold-to-open-options, and directional navigation.
53-
Clickpad directions/Select arrive through Qt's UIKit mapping; indirect swipes
54-
step focus without turning the remote into a pointer. Back/Menu dismisses
55-
overlays or navigates back and remains unhandled at the root for tvOS to return
56-
to the launcher. Play/Pause toggles playback. Siri, TV/Home, and volume remain
57-
system-owned. Video pauses when hidden/suspended; audio uses the playback
58-
session and system Now Playing/remote controls. Idle inhibition applies only
59-
while media is active. Account secrets use device-only Keychain records.
60-
Database/artwork/log storage is purgeable on tvOS; local filesystem browsing
61-
and self-updating are not enabled.
62-
63-
Every tvOS build enables `SPOOL_APPLE_APP_STORE`: only provider pins explicitly
64-
approved with `appleAppStore: true` are embedded, all local overrides are
65-
rejected, and runtime provider-code downloads are disabled. Provider updates
66-
therefore arrive only with app updates, not through a curated download store.
67-
6814
## Local sibling providers
6915

7016
Keep the app checkout and provider repositories together, for example
@@ -667,6 +613,11 @@ PRs restore without publishing caches. Only `spool-tvos-device-arm64` is a publi
667613
release artifact. Simulator apps and smoke results use
668614
`internal-tvos-simulator-arm64` and are never offered as installable downloads.
669615

616+
Before installation, the smoke helper ad-hoc signs both simulator bundles with
617+
matching local application and Keychain access-group entitlements. This requires
618+
no Apple developer certificate, exercises the real sandboxed Keychain consumer,
619+
and does not sign or provision the separately built device IPA.
620+
670621
## Android development
671622

672623
The Android toolchain is pinned to SDK 36, Build Tools 36.0.0 and NDK

‎tools/apple/smoke-tvos.sh‎

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,46 @@ cleanup() { xcrun simctl shutdown "$device" >/dev/null 2>&1 || true; xcrun simct
1212
trap cleanup EXIT
1313
xcrun simctl boot "$device"
1414
xcrun simctl bootstatus "$device" -b
15-
xcrun simctl install "$device" "$app"
1615
# simctl's console mode returns after process exit. Parse only the exact
1716
# credential-free consumer-test result, never dump full app/provider logs.
1817
python3 - "$device" "$app" "$result" <<'PY'
19-
import json, os, re, subprocess, sys
18+
import json, os, plistlib, re, subprocess, sys, tempfile
2019
from pathlib import Path
2120
2221
device, app, output = sys.argv[1:]
2322
output = Path(output)
23+
app = Path(app)
24+
smoke = app.parent / "smoke" / "spool-tvos-playback-smoke.app"
25+
bundle_ids = []
26+
with tempfile.TemporaryDirectory(prefix="spool-tvos-sign-") as signing:
27+
for bundle in (app, smoke):
28+
with (bundle / "Info.plist").open("rb") as metadata:
29+
identifier = plistlib.load(metadata)["CFBundleIdentifier"]
30+
bundle_ids.append(identifier)
31+
# Simulator Keychain still enforces signed application/access-group
32+
# entitlements. A local ad-hoc identity needs no developer certificate
33+
# and is not a signing identity for device/App Store distribution.
34+
application = "SPOOLSMOKE." + identifier
35+
entitlements = Path(signing) / (identifier + ".plist")
36+
with entitlements.open("wb") as file:
37+
plistlib.dump({
38+
"application-identifier": application,
39+
"com.apple.developer.team-identifier": "SPOOLSMOKE",
40+
"keychain-access-groups": [application],
41+
}, file)
42+
subprocess.run(["codesign", "--force", "--sign", "-", "--timestamp=none",
43+
"--entitlements", str(entitlements), "--generate-entitlement-der", str(bundle)], check=True)
44+
subprocess.run(["xcrun", "simctl", "install", device, str(bundle)], check=True)
45+
app_id, smoke_id = bundle_ids
2446
checks = [
25-
("com.sachk.spool", ["--launch-test"], "launch test: application UI rendered"),
26-
("com.sachk.spool.playback-smoke", ["mpv-video-item"],
47+
(app_id, ["--launch-test"], "launch test: application UI rendered"),
48+
(smoke_id, ["mpv-video-item"],
2749
"mpv video smoke: upright frames and OSD rendered across detach and resize"),
28-
("com.sachk.spool.playback-smoke", ["tvos-audio"],
50+
(smoke_id, ["tvos-audio"],
2951
"tvOS audio smoke: AudioUnit output advanced with exclusive playback session"),
30-
("com.sachk.spool.playback-smoke", ["tvos-credentials"],
52+
(smoke_id, ["tvos-credentials"],
3153
"tvOS credentials smoke: Keychain roundtrip and sandbox file persistence passed"),
3254
]
33-
smoke = Path(app).parent / "smoke" / "spool-tvos-playback-smoke.app"
34-
subprocess.run(["xcrun", "simctl", "install", device, str(smoke)], check=True)
3555
results = {}
3656
for bundle, arguments, expected in checks:
3757
environment = os.environ.copy()

0 commit comments

Comments
 (0)