Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
245164c
chore(release): upgrade engine to Flutter 3.44.2 (1.3.1)
MAUstaoglu Jun 14, 2026
f9d655a
fix(platform): make tvOS platform identity correct in AOT/release builds
MAUstaoglu Jun 16, 2026
409d812
fix(rcu): retry the configure handshake until the native plugin is ready
MAUstaoglu Jun 16, 2026
08581da
docs(changelog): document the AOT platform-identity fix in 1.3.1
MAUstaoglu Jun 16, 2026
5882954
fix(platform): compile profile AOT against the non-product host SDK
MAUstaoglu Jun 16, 2026
987f4c8
docs(changelog): note on-device debug (JIT/hot reload) fix for tvOS 26
MAUstaoglu Jun 16, 2026
b5bf86d
test(artifacts): assert engine variant selection per build mode
MAUstaoglu Jun 16, 2026
26cf7da
rcu: report configure-handshake exhaustion + narrow retry catch (PR #…
MAUstaoglu Jun 16, 2026
294b8b6
fix(precache): fetch only tvOS + universal artifacts
MAUstaoglu Jun 19, 2026
34e87f8
fix(create): exit with usage when no output directory given
MAUstaoglu Jun 19, 2026
e1e4d55
docs(changelog): note precache scope + create usage fixes
MAUstaoglu Jun 19, 2026
d30f330
Merge pull request #25 from fluttertv/fix/cli-precache-and-create
MAUstaoglu Jun 22, 2026
d8d042e
chore: upgrade to Flutter 3.44.3 (1.3.2)
MAUstaoglu Jun 22, 2026
9797085
docs: refresh READMEs for 3.44.3 and flutter_tvos 1.1.1
MAUstaoglu Jun 22, 2026
5e5f664
release(flutter_tvos): 1.1.2 (docs refresh)
MAUstaoglu Jun 22, 2026
7d5e358
chore: gitignore per-user SwiftPM/Xcode state under plugin tvos/
MAUstaoglu Jun 22, 2026
930e23d
Merge pull request #26 from fluttertv/feat/flutter-3.44.3-upgrade
MAUstaoglu Jun 22, 2026
75ac599
Merge remote-tracking branch 'origin/main' into dev
MAUstaoglu Jun 22, 2026
dc1a64d
refactor(precache): testable artifact selection + harden flag parsing…
MAUstaoglu Jun 22, 2026
7c91a9c
test: drop the create no-args test (transitively invokes Pub in CI)
MAUstaoglu Jun 22, 2026
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
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

All notable changes to flutter-tvos will be documented here.

## [1.3.2] — 2026-06-22

Refreshes the pinned engine to **Flutter 3.44.3** (`e1fd963c`, Dart
`d684a576`).

### Changed
- Bumped `bin/internal/flutter.version` to `e1fd963c` (Flutter 3.44.3) and
`bin/internal/engine.version` to `v1.0.0-flutter3.44.3`.
- Rebuilt all six engine artifact variants from the Flutter 3.44.3 source tree.
This carries the 3.44.3 engine fixes — notably the APNG image decoder
(`lib/ui/painting/image_generator_apng`). Dart is unchanged (`d684a576`), so
the SDK hash is stable and AOT loads kernel without an `Invalid SDK hash`
mismatch.
- All 17 tvOS engine patches apply cleanly on the 3.44.3 tree.
- Picks up the Flutter 3.44.3 `flutter_tools` fixes that ship in the SDK
(asset hashing, `error_handling_io`, Swift Package Manager, Dart language
version), since the CLI wraps the pinned SDK unmodified.
- Bundled `flutter_tvos` plugin updated to 1.1.2 (README/docs refresh).
- **`flutter-tvos precache` no longer downloads other platforms' artifacts.**
With no platform flags it now fetches only the tvOS engine set plus the
universal artifacts (fonts, patched SDK, host USB-deploy tools); the Android,
iOS-engine, web, and macOS SDKs are skipped. The stock per-platform flags
(`--ios`, `--android`, `--all-platforms`, …) still work when passed
explicitly. The tvOS engine artifacts also now render under a **tvOS Engine**
header in the same nested tree style as the bundled Flutter SDK.

### Fixed
- **`flutter-tvos create` with no output directory** now prints the usage
message and exits with code 2 (matching stock `flutter create`) instead of
crashing with `Bad state: No element`.

## [1.3.1] — 2026-06-16

Refreshes the pinned engine to **Flutter 3.44.2** (`c9a6c484`, Dart
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.3.0`
- Flutter SDK: `3.44.1` (`924134a44c189315be2148659913dda1671cbe99`)
- tvOS engine artifacts: `v1.0.0-flutter3.44.1`
- flutter-tvos: `1.3.2`
- Flutter SDK: `3.44.3` (`e1fd963c6f6922bd32afde2e9698a363cd0406d2`)
- tvOS engine artifacts: `v1.0.0-flutter3.44.3`

## 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.0-flutter3.44.2
v1.0.0-flutter3.44.3
2 changes: 1 addition & 1 deletion bin/internal/flutter.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c9a6c484230f8b5e408ec57be1ef71dee1e77020
e1fd963c6f6922bd32afde2e9698a363cd0406d2
6 changes: 6 additions & 0 deletions lib/commands/create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class TvosCreateCommand extends CreateCommand {

@override
Future<FlutterCommandResult> runCommand() async {
// Mirror stock `flutter create`: print the friendly usage message and exit
// (code 2) when no output directory is given — or more than one — instead
// of crashing on `rest.first` ("Bad state: No element"). The tvos-only path
// below reads `rest.first` before delegating to `super.runCommand()`, so
// validate up front.
validateOutputDirectoryArg();
final String projectDirPath = argResults!.rest.first;
final String name =
stringArg('project-name') ?? globals.fs.path.basename(projectDirPath);
Expand Down
80 changes: 79 additions & 1 deletion lib/commands/precache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import 'package:file/src/interface/directory.dart';
import 'package:flutter_tools/src/commands/precache.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:meta/meta.dart';

import '../tvos_cache.dart';

Expand All @@ -20,6 +22,44 @@ class TvosPrecacheCommand extends PrecacheCommand {
argParser.addFlag('tvos', defaultsTo: true, help: 'Precache artifacts for tvOS development.');
}

// The `--android` umbrella flag stands in for its three child artifacts.
static const Map<String, String> _umbrellaForArtifact = <String, String>{
'android_gen_snapshot': 'android',
'android_maven': 'android',
'android_internal_build': 'android',
};

// Non-platform artifacts a tvOS build always needs (fonts, sky_engine,
// flutter_patched_sdk, font-subset, host USB-deploy tools, engine stamp).
static const Set<String> _alwaysOn = <String>{'universal', 'informative'};

/// The non-tvOS artifacts to fetch for the given flags. With no platform
/// flags this is only [_alwaysOn]; `--all-platforms` and explicit per-platform
/// flags add their artifacts, and a flag for an `--android` child works either
/// via `--android` or the child's own flag. Feature-gated platforms are
/// skipped when their feature is disabled. Pure (no I/O) so it is unit-tested
/// directly — see `test/general/tvos_precache_test.dart`.
@visibleForTesting
static Set<DevelopmentArtifact> selectRequiredArtifacts({
required FeatureFlags featureFlags,
required bool allPlatforms,
required bool Function(String flagName) isFlagOn,
}) {
final requiredArtifacts = <DevelopmentArtifact>{};
for (final DevelopmentArtifact artifact in DevelopmentArtifact.values) {
if (artifact.feature != null && !featureFlags.isEnabled(artifact.feature!)) {
continue;
}
final String? umbrella = _umbrellaForArtifact[artifact.name];
final bool explicitlyRequested =
isFlagOn(artifact.name) || (umbrella != null && isFlagOn(umbrella));
if (allPlatforms || _alwaysOn.contains(artifact.name) || explicitlyRequested) {
requiredArtifacts.add(artifact);
}
}
return requiredArtifacts;
}

@override
Future<FlutterCommandResult> runCommand() async {
if (boolArg('tvos')) {
Expand All @@ -31,6 +71,44 @@ class TvosPrecacheCommand extends PrecacheCommand {
}
await globals.cache.updateAll(<DevelopmentArtifact>{TvosDevelopmentArtifact.tvos});
}
return super.runCommand();

// Stock `flutter precache` with no platform flags downloads *every* enabled
// platform's artifacts (Android, iOS, web, macOS). A tvOS embedder needs
// none of those — only the universal artifacts (fonts, sky_engine,
// flutter_patched_sdk, font-subset) and the engine stamp, on top of the
// tvOS engine set fetched above. So drive the cache ourselves instead of
// delegating to `super.runCommand()`, while still honouring the stock
// per-platform flags (`--ios`, `--android`, `--all-platforms`, …) for anyone
// who explicitly asks for them.
if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
await globals.cache.lock();
}
if (boolArg('force')) {
globals.cache.clearStampFiles();
}
final bool allPlatforms = boolArg('all-platforms');
if (allPlatforms) {
globals.cache.includeAllPlatforms = true;
}
if (boolArg('use-unsigned-mac-binaries')) {
globals.cache.useUnsignedMacBinaries = true;
}

final Set<DevelopmentArtifact> requiredArtifacts = selectRequiredArtifacts(
featureFlags: featureFlags,
allPlatforms: allPlatforms,
// `ArgResults.wasParsed` throws on an option the command never defined, so
// guard with `options.containsKey` — a future Flutter `DevelopmentArtifact`
// without a matching precache flag then can't crash us.
isFlagOn: (String name) =>
argParser.options.containsKey(name) && argResults!.wasParsed(name) && boolArg(name),
);

// `updateAll` is idempotent — it checks each artifact's stamp and re-downloads
// only what is stale, so there is no need (and no reliable way, since the
// cache also tracks platforms we intentionally skip) to short-circuit on a
// global `isUpToDate()` check.
await globals.cache.updateAll(requiredArtifacts);
return FlutterCommandResult.success();
}
}
4 changes: 4 additions & 0 deletions lib/plugin_porting/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ tvos/.symlinks/
tvos/Flutter/Flutter.framework
tvos/Flutter/Flutter.podspec

# Xcode / SwiftPM (per-user, generated when tvos/Package.swift is opened)
**/.swiftpm/
**/xcuserdata/

# IDE
.idea/
.vscode/
Expand Down
34 changes: 27 additions & 7 deletions lib/tvos_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ class TvosEngineArtifacts extends EngineCachedArtifact {
'host_release.zip',
];

// The artifact-group header printed by `Cache.updateAll` — defaults to the
// (stamp) `name`, which reads as a bare "engine". Label it "tvOS Engine" so
// the precache output names the toolchain it belongs to.
@override
String get displayName => 'tvOS Engine';

@override
Directory get location => tvosArtifactDirectory(globals.fs);

Expand Down Expand Up @@ -194,14 +200,16 @@ class TvosEngineArtifacts extends EngineCachedArtifact {
);

try {
var index = 0;
for (final String zipName in _artifactZipNames) {
index++;
final String url = artifactDownloadUrl(zipName);
final File tempZip = tempDir.childFile(zipName);
// Format mirrors stock Flutter cache:
// `Downloading <name> tools... 1,339ms`
// The Status object writes the elapsed time on stop().
// Render as a child of the framework-printed `[i/N] engine` header,
// mirroring stock Flutter's nested artifact tree. The Status object
// writes the elapsed time on stop().
final Status status = _logger.startProgress(
'Downloading ${_friendlyName(zipName)} tools...',
_treeLine(index, _artifactZipNames.length, _friendlyName(zipName)),
);
try {
final RunResult curlResult = await _processUtils.run(<String>[
Expand Down Expand Up @@ -264,9 +272,11 @@ class TvosEngineArtifacts extends EngineCachedArtifact {
}
location.createSync(recursive: true);

var index = 0;
for (final zip in zips) {
index++;
final Status status = _logger.startProgress(
'Extracting ${_friendlyName(zip.basename)} tools...',
_treeLine(index, zips.length, _friendlyName(zip.basename)),
);
try {
final RunResult result = await _processUtils.run(<String>[
Expand All @@ -293,9 +303,19 @@ class TvosEngineArtifacts extends EngineCachedArtifact {
_makeFilesExecutable(location, operatingSystemUtils);
}

/// Formats one zip's progress line as a child of the framework-printed
/// `[i/N] engine` header, mirroring stock Flutter's nested artifact tree
/// (see `ArtifactUpdater.formatProgressMessage`): ` ├─ [1/6] tvos-debug-…`,
/// with `└─` on the final entry. The elapsed time is appended by the
/// [Status] on stop, exactly like the bundled "Flutter SDK" artifact.
String _treeLine(int index, int total, String name) {
final prefix = index == total ? '└─' : '├─';
return ' $prefix [$index/$total] $name';
}

/// Converts a zip filename to the human-readable label that goes into the
/// `Downloading … tools...` line. Mirrors stock Flutter's `<target>/<host>`
/// format using hyphens.
/// progress line. Mirrors stock Flutter's `<target>/<host>` format using
/// hyphens.
///
/// Examples:
/// tvos_debug_sim_arm64.zip → tvos-debug-sim-arm64
Expand Down
6 changes: 6 additions & 0 deletions packages/flutter_tvos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.1.2

- Documentation: list Siri Remote support in the README feature summary and
bump the install snippet to the current version. No functional or API
changes.

## 1.1.1

- Fixed the remote-control `configure` handshake racing native plugin
Expand Down
5 changes: 4 additions & 1 deletion packages/flutter_tvos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ Part of the [flutter-tvos](https://fluttertv.dev) project — an open-source Flu
- Check device capabilities: 4K, HDR, multi-user support
- Get display resolution
- **Synchronous API** — powered by dart:ffi, zero async overhead
- **Siri Remote support** — swipes and buttons drive Flutter's standard
focus system, with raw-touch and high-level swipe listeners for custom
handling (see [Remote Control](#remote-control-siri-remote))

## Getting Started

Add `flutter_tvos` to your `pubspec.yaml`:

```yaml
dependencies:
flutter_tvos: ^1.0.4
flutter_tvos: ^1.1.2
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tvos/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.1.0"
version: "1.1.2"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tvos/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_tvos
description: Platform detection and utilities for Flutter apps running on Apple TV (tvOS). Provides runtime checks for tvOS, device info, and capability queries.
version: 1.1.1
version: 1.1.2
homepage: https://fluttertv.dev
repository: https://github.com/fluttertv/flutter-tvos
issue_tracker: https://github.com/fluttertv/flutter-tvos/issues
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_tvos/tvos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Per-user Xcode / SwiftPM local state, generated when the tvos/Package.swift
# is opened in Xcode. Not source — never commit.
.swiftpm/
xcuserdata/
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.3.1
version: 1.3.2
homepage: https://fluttertv.dev
repository: https://github.com/fluttertv/flutter-tvos
publish_to: "none"
Expand Down
Loading
Loading