Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 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
a9d7a8d
Merge remote-tracking branch 'origin/main' into dev
MAUstaoglu Jul 17, 2026
57d585d
docs: PR template + contributing guide for the dev-branch workflow
MAUstaoglu Jul 17, 2026
fd8847d
docs(changelog): add [Unreleased] section for incoming PR entries
MAUstaoglu Jul 17, 2026
23692b2
Skip plugin registration with a clear message when the engine isn't r…
SupposedlySam Jul 17, 2026
7014a93
Bump engine and SDK to Flutter 3.44.6, fix tvOS<26 simulator crash (1…
MAUstaoglu Jul 20, 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 .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
⚠️ Base branch: PRs must target `dev`, not `main`.
`main` only receives release merges from `dev`. If your PR targets `main`,
edit the base branch (Edit button next to the PR title) before requesting review.
-->

## What does this PR do?

<!-- A clear description of the change and the motivation behind it.
Link related issues: "Fixes #123" / "Part of #123". -->

## How was it tested?

<!-- Check all that apply and describe the setup (tvOS/Xcode versions, device model). -->

- [ ] `flutter/bin/dart test test/` passes
- [ ] `flutter/bin/dart analyze lib/` is clean
- [ ] Verified on tvOS **simulator**
- [ ] Verified on a **physical Apple TV**

## Checklist

- [ ] This PR targets the **`dev`** branch (not `main`)
- [ ] Added an entry to `CHANGELOG.md` under **`[Unreleased]`** (user-visible changes only; no version bump — maintainers assign versions at release)
- [ ] Added or updated tests covering the change (or explained below why none are needed)
- [ ] If the change touches generated plugin registrants or app templates: **both** the ObjC and Swift variants are updated
- [ ] No changes under `flutter/` or `engine_artifacts/` (managed directories — see CONTRIBUTING.md)

## Notes for reviewers

<!-- Anything that needs extra attention: trade-offs, follow-ups, areas you're unsure about. -->
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

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

## [Unreleased]

<!-- 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. -->

## [1.4.1] — 2026-07-19

Refreshes the pinned engine to **Flutter 3.44.6** (`ee80f08b`, Dart 3.12.2).

### Engine / SDK
- Bumped `bin/internal/flutter.version` to `ee80f08b` (Flutter 3.44.6) and
`bin/internal/engine.version` to `v1.0.1-flutter3.44.6`.
- Rebuilt and origin-signed all six engine artifact variants from the 3.44.6
source tree, against tvOS SDK 26.5 (Xcode 26.6).
- All 17 tvOS engine patches apply unchanged: the 3.44.5 → 3.44.6 delta touches
only `flutter_tools` and the Android Gradle plugin, so no patched file moved.
- Dart revision is unchanged from 3.44.5, so this is not an SDK-hash-breaking
roll — the artifacts were rebuilt anyway to keep the engine tag and the SDK
pin in lockstep.

### Fixed
- **Apps no longer crash on first frame on tvOS simulators older than 26.** The
simulator engine embedded Impeller metallibs compiled for the *iOS* simulator
(target triple `air64-apple-ios13.0.0-simulator`), because the shader build
assumed the tvOS simulator would load iphonesimulator libraries fine — it runs
on the host Mac GPU, after all. Metal actually validates the library's target
platform when a pipeline is created, so on a tvOS 17.5 simulator every one of
the ~60 render pipelines failed with `Target OS is incompatible` and the engine
aborted with a `raw_ptr` check failure. tvOS 26 tolerates the mismatch, which
is why it only showed up on older runtimes. The simulator engine is now built
against the AppleTVSimulator SDK (`air64-apple-tvos13.0.0-simulator`). Device
builds were never affected. Requires the engine bump to
`v1.0.1-flutter3.44.6`. (#38)
- **Debug builds launched outside `flutter-tvos run` on a physical Apple TV no
longer crash during plugin registration.** The debug engine refuses to start
without an attached debugger, so the plugin registry hands back nil
registrars — which crashed the generated registrant with an unsymbolized
SIGSEGV blaming whichever plugin registered first. The registrant now probes
the registry once, logs a clear "engine is not running" message explaining
the `flutter-tvos run` requirement, and skips registration instead of
crashing. (#37)

### Upstream fixes picked up
- Flutter 3.44.6 hotfixes for a native-assets crash on Linux custom devices and
for jniLibs staging in the Android Gradle plugin. Neither affects tvOS builds;
they ship because the SDK pin moves as a whole.

## [1.4.0] — 2026-07-11

Ships the last of the App Store submission blockers: the engine artifact is now
Expand Down
19 changes: 16 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,28 @@ Open an issue on [GitHub Issues](https://github.com/fluttertv/flutter-tvos/issue

The more detail you provide, the faster the bug can be tracked down.

## Branching Model

- **`main`** holds released code only. Every commit on `main` corresponds to a published release (or the release process itself). Nothing is merged into `main` directly.
- **`dev`** is the integration branch. All pull requests target `dev`. It always contains the latest `main` plus the changes queued for the next release.
- At release time, maintainers merge `dev` into `main`, tag, and publish; `main` is then merged back into `dev` so the two stay in sync.

## Submitting a Pull Request

1. Fork the repository and create a branch from `main`
1. Fork the repository and create a branch from `dev`
2. Make your changes
3. Run the test suite and static analysis (see below)
4. Open a PR against `main` with a clear description of what changed and why
3. Add a `CHANGELOG.md` entry under `[Unreleased]` (see below)
4. Run the test suite and static analysis (see below)
5. Open a PR against **`dev`** with a clear description of what changed and why

Keep PRs focused. One logical change per PR makes review faster.

## Changelog

User-visible changes (fixes, features, behavior changes) get an entry in `CHANGELOG.md` under the `[Unreleased]` heading, written in the same style as the released sections. **Do not bump the version** in `pubspec.yaml` or add a version heading — maintainers assign version numbers when cutting a release, at which point `[Unreleased]` entries are moved into the new version's section.

Internal-only changes (refactors, CI, test-only changes) don't need a changelog entry.

## Running Tests

The Flutter SDK is bootstrapped automatically into `flutter/` when you first run any `flutter-tvos` command. Once it is present, run the full test suite from the `flutter-tvos/` directory:
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.0`
- Flutter SDK: `3.44.5` (`f94f4fc76b4d74543ed9b085bbd75341ef65de22`)
- tvOS engine artifacts: `v1.0.1-flutter3.44.5` (origin-signed)
- flutter-tvos: `1.4.1`
- Flutter SDK: `3.44.6` (`ee80f08bbf97172ec030b8751ceab557177a34a6`)
- tvOS engine artifacts: `v1.0.1-flutter3.44.6` (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.5
v1.0.1-flutter3.44.6
2 changes: 1 addition & 1 deletion bin/internal/flutter.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f94f4fc76b4d74543ed9b085bbd75341ef65de22
ee80f08bbf97172ec030b8751ceab557177a34a6
6 changes: 6 additions & 0 deletions doc/debug-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ xcrun devicectl device process launch --console \

`<device_id>` is the UUID shown by `xcrun devicectl list devices`. The `--console` flag attaches stdout/stderr to your terminal session.

> **Note:** standalone launches like this only work for **profile and release**
> builds. A debug build launched outside `flutter-tvos run` cannot start its
> engine (the debug engine requires an attached debugger) — plugin
> registration is skipped and a `[GeneratedPluginRegistrant]` message
> explaining this appears in the console.

## Other Resources

- [Flutter debugging documentation](https://docs.flutter.dev/testing/debugging) — core Dart and Flutter debugging concepts that apply equally to tvOS.
Expand Down
27 changes: 27 additions & 0 deletions lib/tvos_plugins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ import {{name}}
{{/methodChannelPlugins}}

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
// The registry returns nil registrars when the Flutter engine is not
// running (on a physical Apple TV the debug engine requires an attached
// debugger). Nil crashes plugin registration, so bail out loudly.
guard registry.registrar(forPlugin: "__flutter_tvos_engine_probe__") != nil else {
NSLog("[GeneratedPluginRegistrant] Flutter engine is not running; skipping plugin registration. Debug builds on a physical Apple TV must be launched via 'flutter-tvos run' (the debug engine requires an attached debugger).")
return
}
{{#methodChannelPlugins}}
{{class}}.register(with: registry.registrar(forPlugin: "{{class}}"))
{{/methodChannelPlugins}}
Expand Down Expand Up @@ -677,6 +684,25 @@ Future<void> ensureReadyForTvosTooling(FlutterProject project) async {
}
}

// A nil registrar means the engine never started (on a physical Apple TV
// the debug engine requires an attached debugger — see
// doc/architecture.md). Bridged into a Swift plugin's nonnull parameter,
// that nil crashes registration with an unsymbolized SIGSEGV blaming the
// first plugin. Probe once with a synthetic key (a real plugin key would
// trip the engine's duplicate-registration assert) and bail out loudly.
final engineGuard = registrations.isEmpty
? ''
: ' // The registry returns nil registrars when the Flutter engine is not\n'
' // running (on a physical Apple TV the debug engine requires an attached\n'
' // debugger). Nil crashes Swift plugin registration, so bail out loudly.\n'
' if ([registry registrarForPlugin:@"__flutter_tvos_engine_probe__"] == nil) {\n'
' NSLog(@"[GeneratedPluginRegistrant] Flutter engine is not running; skipping "\n'
' @"plugin registration. Debug builds on a physical Apple TV must be "\n'
' @"launched via \'flutter-tvos run\' (the debug engine requires an "\n'
' @"attached debugger).");\n'
' return;\n'
' }\n';

// Force-reference the C symbols of any statically-linked (SPM) FFI plugin
// so the linker keeps them in the Runner binary; empty for the common case.
final List<String> ffiForcedSymbols =
Expand Down Expand Up @@ -721,6 +747,7 @@ Future<void> ensureReadyForTvosTooling(FlutterProject project) async {
'@implementation GeneratedPluginRegistrant\n'
'\n'
'+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {\n'
'$engineGuard'
'$registrations'
'$ffiForcedBody'
'}\n'
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.0
version: 1.4.1
homepage: https://fluttertv.dev
repository: https://github.com/fluttertv/flutter-tvos
publish_to: "none"
Expand Down
102 changes: 102 additions & 0 deletions test/general/tvos_plugins_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,108 @@ $symbolsYaml
);
});

group('engine guard in GeneratedPluginRegistrant.m', () {
// Seeds an app with a single method-channel plugin `gadget` plus the
// tvos/Runner/ directory the ObjC registrant is written into.
FlutterProject seedMethodChannelProject() {
final Directory projectDir = fileSystem.directory('/p')..createSync();
projectDir.childDirectory('tvos').childDirectory('Runner').createSync(recursive: true);
projectDir.childFile('pubspec.yaml').writeAsStringSync('name: app\n');

final Directory pkgDir = fileSystem.directory('/pubcache/gadget')
..createSync(recursive: true);
pkgDir.childFile('pubspec.yaml').writeAsStringSync('''
name: gadget
flutter:
plugin:
platforms:
tvos:
pluginClass: GadgetPlugin
''');
final Directory tvosDir = pkgDir.childDirectory('tvos')..createSync();
tvosDir.childFile('gadget.podspec').writeAsStringSync('# podspec');

fileSystem.directory('/p/.dart_tool').childFile('package_config.json')
..createSync(recursive: true)
..writeAsStringSync(
json.encode(<String, dynamic>{
'packages': <Map<String, String>>[
<String, String>{
'name': 'gadget',
'rootUri': 'file:///pubcache/gadget',
},
],
}),
);
projectDir.childFile('.flutter-plugins-dependencies').writeAsStringSync(
json.encode(<String, dynamic>{
'dependencyGraph': <Map<String, String>>[
<String, String>{'name': 'gadget'},
],
}),
);
return FlutterProject.fromDirectory(projectDir);
}

String registrantOf(FlutterProject project) => project.directory
.childDirectory('tvos')
.childDirectory('Runner')
.childFile('GeneratedPluginRegistrant.m')
.readAsStringSync();

testUsingContext(
'guards registration behind an engine probe when method-channel plugins exist',
() async {
final FlutterProject project = seedMethodChannelProject();
await ensureReadyForTvosTooling(project);

final String m = registrantOf(project);
expect(m, contains('registrarForPlugin:@"__flutter_tvos_engine_probe__"'));
expect(m, contains('Flutter engine is not running'));
// The guard must run before any plugin registration.
expect(
m.indexOf('__flutter_tvos_engine_probe__'),
lessThan(m.indexOf('[GadgetPlugin registerWithRegistrar:')),
reason: 'engine probe must precede the first registration',
);
},
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
},
);

testUsingContext(
'omits the guard when there are no method-channel plugins',
() async {
final Directory projectDir = fileSystem.directory('/p')..createSync();
projectDir
.childDirectory('tvos')
.childDirectory('Runner')
.createSync(recursive: true);
projectDir.childFile('pubspec.yaml').writeAsStringSync('name: app\n');
fileSystem.directory('/p/.dart_tool').childFile('package_config.json')
..createSync(recursive: true)
..writeAsStringSync(json.encode(<String, dynamic>{'packages': <dynamic>[]}));
projectDir.childFile('.flutter-plugins-dependencies').writeAsStringSync(
json.encode(<String, dynamic>{'dependencyGraph': <dynamic>[]}),
);

final FlutterProject project = FlutterProject.fromDirectory(projectDir);
await ensureReadyForTvosTooling(project);

expect(
registrantOf(project),
isNot(contains('__flutter_tvos_engine_probe__')),
);
},
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
},
);
});

group('TvosPlugin.ffiSymbols', () {
testWithoutContext('defaults to an empty list', () {
final plugin = TvosPlugin(name: 'm', path: '/p', pluginClass: 'MPlugin');
Expand Down
Loading