Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,41 @@ All notable changes to flutter-tvos will be documented here.

## [Unreleased]

## [1.4.2] - 2026-07-21

### Changed

- Bumped the pinned Flutter SDK to **3.44.7** (`84fc5cbb223b`).
- Engine artifacts are now `v1.0.1-flutter3.44.7`. These are **byte-identical**
to `v1.0.1-flutter3.44.6`, republished under the new name rather than
rebuilt: nothing in 3.44.7 reaches the tvOS engine. The only functional
change in the range is Android-side (`FlutterRenderer.java`), `DEPS` is
byte-identical, the Dart submodule is unchanged, and `darwin/`, `ios/` and
`impeller/` are untouched. The remaining commits are CI config and changelog
text.

Verified with these artifacts against the 3.44.7 SDK before shipping: AOT on a
physical Apple TV 4K (tvOS 26.5) keeps platform identity intact
(`operatingSystem=tvos`, `isTvOS=true`), and a JIT/debug run on a tvOS 17.5
simulator is clean with zero `Target OS is incompatible` errors, so the
metallib fix from 1.4.1 still holds on older runtimes.

### Fixed

- `Platform.isTvOS` now compiles in **debug** builds. It previously worked only
in profile/release: debug compiled against the stock Flutter SDK, which does
not declare the getter, so an app using the API the README recommends failed
to build with `Member not found: 'isTvOS'`. Present since the getter was
introduced — reproduced identically on 1.4.1 — and easy to miss because the
*values* (`operatingSystem`, `isIOS`) resolve at runtime from the device
engine in JIT and are correct in every mode; only the new member was missing
from the type checker's view.

Debug now compiles against the same patched `flutter_patched_sdk` as profile,
which the `host_debug_unopt` artifact already shipped. Verified on a tvOS 17.5
simulator (`isTvOS=true` in debug, `isAndroid`/`isMacOS` unaffected), AOT on a
physical Apple TV 4K is unchanged, and hot reload still works.

<!-- Contributors: add entries for user-visible changes here (see CONTRIBUTING.md).
Do not add a version heading or bump pubspec.yaml — maintainers assign
versions at release time. -->
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ A Flutter toolchain for building and running Flutter apps on **Apple TV (tvOS)**

## Current version

- flutter-tvos: `1.4.1`
- Flutter SDK: `3.44.6` (`ee80f08bbf97172ec030b8751ceab557177a34a6`)
- tvOS engine artifacts: `v1.0.1-flutter3.44.6` (origin-signed)
- flutter-tvos: `1.4.2`
- Flutter SDK: `3.44.7` (`84fc5cbb223bc12f83d65b647ff8a56caf779ffd`)
- tvOS engine artifacts: `v1.0.1-flutter3.44.7` (origin-signed)

## Installation

Expand Down
2 changes: 1 addition & 1 deletion bin/internal/engine.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.0.1-flutter3.44.6
v1.0.1-flutter3.44.7
2 changes: 1 addition & 1 deletion bin/internal/flutter.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ee80f08bbf97172ec030b8751ceab557177a34a6
84fc5cbb223bc12f83d65b647ff8a56caf779ffd
38 changes: 21 additions & 17 deletions lib/tvos_artifacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,28 @@ class TvosArtifacts extends CachedArtifacts {
}
}

// For AOT (profile/release) builds, compile the app kernel against OUR
// patched `flutter_patched_sdk` (shipped inside the host engine artifact)
// rather than the stock Flutter checkout's. The patched dart:io
// `platform.dart` defines `isIOS = operatingSystem == "ios" || == "tvos"`
// and adds the `isTvOS` getter, so the un-folded platform-const getters
// evaluate correctly at runtime on tvOS. This is the companion to
// `TvosKernelSnapshot.build()`, which passes `targetOS: null` so those
// getters are not const-folded to "ios" at compile time.
// Compile the app kernel against OUR patched `flutter_patched_sdk`
// (shipped inside the host engine artifact) rather than the stock Flutter
// checkout's. The patched dart:io `platform.dart` defines
// `isIOS = operatingSystem == "ios" || == "tvos"` and adds the `isTvOS`
// getter, so the un-folded platform-const getters evaluate correctly at
// runtime on tvOS. This is the companion to `TvosKernelSnapshot.build()`,
// which passes `targetOS: null` so those getters are not const-folded to
// "ios" at compile time.
//
// Debug (JIT) deliberately keeps the stock SDK: platform identity there is
// resolved by the device engine's own (patched) core libraries at runtime,
// so the compile SDK is irrelevant and we avoid disturbing the proven
// debug path. We only need our patched SDK where gen_snapshot bakes the
// SDK code into the app snapshot — i.e. precompiled builds.
if ((mode?.isPrecompiled ?? false) &&
(artifact == Artifact.flutterPatchedSdkPath ||
artifact == Artifact.platformKernelDill)) {
final String patchedSdkDir = _hostPatchedSdkDirectory(mode!);
// This applies to debug as well, not just AOT. Platform identity *values*
// do resolve at runtime in JIT — dart:io comes from the device engine's
// own patched core libraries — so for `operatingSystem` and `isIOS` the
// compile SDK genuinely is irrelevant there. But `isTvOS` is a member the
// stock SDK does not declare at all, and the frontend server type-checks
// against the compile SDK before anything runs: an app touching
// `Platform.isTvOS` failed to build in debug with
// `Member not found: 'isTvOS'` while compiling fine in profile/release.
// Pointing debug at the same patched SDK (which `host_debug_unopt` already
// ships) makes the getter exist in every mode.
if (artifact == Artifact.flutterPatchedSdkPath ||
artifact == Artifact.platformKernelDill) {
final String patchedSdkDir = _hostPatchedSdkDirectory(mode ?? BuildMode.debug);
if (artifact == Artifact.platformKernelDill) {
return _fileSystem.path.join(patchedSdkDir, 'platform_strong.dill');
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_tvos
description: Flutter CLI tool for building and running Flutter apps on Apple TV (tvOS). A custom embedder wrapping Flutter SDK with tvOS-specific build targets, device management, and plugin support.
version: 1.4.1
version: 1.4.2
homepage: https://fluttertv.dev
repository: https://github.com/fluttertv/flutter-tvos
publish_to: "none"
Expand Down
36 changes: 23 additions & 13 deletions test/general/tvos_artifacts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,33 @@ void main() {
);
});

test('debug flutterPatchedSdkPath falls through to stock resolution', () {
// Debug resolves platform identity at runtime via the device engine, so
// the override is intentionally NOT applied — the path must come from
// the stock CachedArtifacts logic, not our engine_artifacts host SDK.
final String path = artifacts.getArtifactPath(
Artifact.flutterPatchedSdkPath,
mode: BuildMode.debug,
test('debug also uses the patched SDK (host_debug_unopt)', () {
// Debug resolves platform identity *values* at runtime via the device
// engine, so for `operatingSystem` and `isIOS` the compile SDK is
// irrelevant. But `isTvOS` is a member the stock SDK does not declare,
// and the frontend server type-checks against the compile SDK before
// anything runs — so with stock resolution an app touching
// `Platform.isTvOS` failed to build in debug with
// `Member not found: 'isTvOS'` while compiling fine in profile/release.
// Debug uses the same non-product SDK as profile.
expect(
artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: BuildMode.debug),
'/engine_artifacts/host_debug_unopt/flutter_patched_sdk',
);
expect(
artifacts.getArtifactPath(Artifact.platformKernelDill, mode: BuildMode.debug),
'/engine_artifacts/host_debug_unopt/flutter_patched_sdk/platform_strong.dill',
);
expect(path, isNot(contains('engine_artifacts')));
});

test('debug platformKernelDill falls through to stock resolution', () {
final String path = artifacts.getArtifactPath(
Artifact.platformKernelDill,
mode: BuildMode.debug,
test('a null mode resolves to the debug (non-product) SDK', () {
// getArtifactPath is reachable without a mode; it must not throw and
// must not silently pick the product SDK, which would drop entry-point
// classes the JIT engine looks up natively.
expect(
artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
'/engine_artifacts/host_debug_unopt/flutter_patched_sdk',
);
expect(path, isNot(contains('engine_artifacts')));
});

test('handles the nested directory layout from zip extraction', () {
Expand Down
Loading