Refresh the 3.32.8 line onto current dev - #70
Merged
MAUstaoglu merged 21 commits intoAug 21, 2026
Conversation
Adds `flutter-tvos versions` and `flutter-tvos use <version>` for switching the checkout between Flutter versions (#50), stops `build` inheriting upstream's platform subcommands (#52), fixes `create .` naming the project "." (#54), and pins the engine to v1.0.2-flutter3.44.8 (#48). `upgrade` now moves the checkout with `git checkout --force --detach` instead of `git reset --hard`, which had silently discarded unpushed commits.
main was released as 1.5.0 but dev never received the release-prep commit, so dev still shows [Unreleased] and version 1.4.3. Carries only the three version-bearing files; no code changes.
3.44.9 pins the same Dart revision (d684a576) as 3.44.8, so the existing v1.0.2-flutter3.44.8 engine artifacts stay AOT-valid — no engine rebuild and no re-signing. The only engine-tree changes in 3.44.9 are Android lint/SDK bundle files, which tvOS does not build. The build-graph overrides in lib/build_targets/application.dart are unaffected: 3.44.9 touches nothing under build_system/ and leaves KernelSnapshot.build() and KernelCompiler.compile untouched.
Bump pinned Flutter SDK to 3.44.9
3.44.9 pins the same Dart revision (d684a576) as 3.44.8, so the existing v1.0.2-flutter3.44.8 engine artifacts stay AOT-valid — no engine rebuild and no re-signing. The only engine-tree changes in 3.44.9 are Android lint/SDK bundle files, which tvOS does not build. The build-graph overrides in lib/build_targets/application.dart are unaffected: 3.44.9 touches nothing under build_system/ and leaves KernelSnapshot.build() and KernelCompiler.compile untouched.
# Conflicts: # CHANGELOG.md # README.md # pubspec.yaml
Merge main into dev (1.5.1 release files)
Upgrades the CLI to Flutter 3.47.0 (4cf24164) and the origin-signed engine-495915c579071a4ef6a5014637166d5231cb4aac artifacts. - Re-mirrors the 3.47.0 flutter_tools API: DartBuild -> LinkHooks, recorded_uses.json in KernelSnapshot, LLDB xcodeProjectInterpreter. - Raises the tvOS deployment floor 13.0 -> 15.0 to match the engine's actual minos, which upstream moved with ios_deployment_target. Apps previously claimed tvOS 13 support while embedding a framework requiring 15 (ITMS-90208 at submission). - Fixes on-device debug: the VM Service URI rewrite now polls mDNS in 5s attempts up to 60s and exits on the explanation when nothing resolves, instead of returning an undialable host and leaving hot reload silently dead. - Verifies any host devicectl offers before trusting it; potentialHostnames stays populated after the CoreDevice tunnel drops and those names resolve for nobody, which would otherwise short-circuit the retry budget. - Adds a workflow that fast-forwards main to the exact tested commit on dev.
The Runner project runs no Dart build. "Embed App.framework" and "Copy flutter_assets" copy whatever the last `flutter-tvos build/run` staged into tvos/Flutter, and the engine is the Flutter.xcframework that same run copied in, so Xcode's CONFIGURATION never had any influence on the Flutter payload. Archiving Release after a debug run therefore shipped the debug/JIT engine and a kernel_blob.bin inside a release app. That build runs from Xcode, because a development signature permits the JIT pages the VM needs. Installed from TestFlight or the App Store, where the distribution signature carries no get-task-allow, the VM cannot get executable pages: the app launches, the launch screen paints, and no Flutter frame ever arrives. Verified end to end - two builds from one source, same Release configuration, differing only in what was staged: the clean one runs, the one archived after a debug run shows a blank screen. Nothing upstream catches it either, altool --validate-app and App Store processing both accept it. Generated.xcconfig now records FLUTTER_BUILD_MODE, and the app template gained a "Check Flutter build mode" phase - first in the target, ahead of anything that copies a payload in - that fails the build on a mismatch and names the command to run. A profile payload under Release only warns, since the CLI drives profile builds through that configuration and they do run; an unset FLUTTER_BUILD_MODE from an older CLI warns rather than fails. project.pbxproj is written once at create time and never rewritten, so existing projects keep archiving unguarded until regenerated. Device builds now warn when the phase is absent.
The existing assertions only prove the phase exists and still contains the right strings. An inverted condition, or a case pattern that stopped matching Release, would leave all of them intact and still archive an app that cannot start - which is the whole failure this phase is meant to prevent. Extract the phase's shellScript from both pbxproj files, unescape it, and run it under /bin/sh against a throwaway project directory: Release staged for debug fails and names the fix, Release staged for release and Debug staged for debug pass, profile under Release and an unset FLUTTER_BUILD_MODE only warn, a release build with no App.framework staged fails, and an unrecognized configuration is skipped rather than guessed at. Checked against a mutant: inverting the mode comparison in the template fails five of the seven cases, while the example's untouched copy stays green.
Moves the pinned SDK to 3.47.1 and the artifacts to the engine built against it, and cuts the release with the archive-mode guard already on this branch. Nothing tvOS-facing moved between 3.47.0 and 3.47.1: none of the files the tvOS patch set touches changed, `shell/platform/darwin` is untouched, and the five Dart SDK files behind the platform-identity patches are identical blobs at both revisions. The engine was rebuilt anyway, because `dart_revision` moved and AOT snapshots are keyed to the Dart SDK hash — profile and release builds against the previous artifacts would fail to load. The two pins move together for that reason. Verified before publishing: 118 engine unit tests green on both tvOS 17.5 and 26.5, and the full build-and-run matrix green on the 17.5 simulator and a physical Apple TV 4K across debug, profile and release — launch, VM service, hot reload and restart, platform identity and FFI symbol export in each. All six artifacts pass their checks, including origin signing on all four tvOS engines.
Review found the guard green in scenarios that need no unusual setup. All four are real; each is addressed here. **The remediation was a no-op.** `renderTvosRunner` returns as soon as `tvos/` exists — before its own status line, and with no `overwrite` parameter for `--overwrite` to reach — so `flutter-tvos create .` did nothing, silently, for every project the warning targets. The user cleared no warning and archived believing they were protected. It now says it declined and why, and the three places that gave that advice (the warning, doc/publish-app.md, CHANGELOG) lead with the mitigation that works today: run the release build immediately before each archive. **The marker was written mid-staging.** `_generateXcconfigs` ran at step 5, after the engine, assets and registrant had already been replaced. A build interrupted in that window left a marker describing a payload no longer on disk: release build, then Ctrl-C a debug build after the JIT engine and kernel_blob were staged but before the marker moved, and the guard blesses issue #65 verbatim. `build()` now writes `unknown` before touching anything and the real value only once staging has finished, so the failure mode is a rejected build rather than an accepted one. **An undeterminable mode passed.** Empty is the state after `flutter-tvos clean`, on a fresh checkout (Generated.xcconfig is gitignored) and with an older CLI — not just the last of those. Its backstop was the App.framework check, which cannot help: nothing in a normal build deletes App.framework, so a release one survives any number of debug builds. Under Debug this still warns; under anything else it now fails. A payload check runs first and is immune to a stale marker entirely: a `kernel_blob.bin` under a non-debug configuration proves the payload is JIT whatever the marker claims. **The marker collided with upstream's override.** `FLUTTER_BUILD_MODE` is what upstream tells users to set themselves, and `Generated.xcconfig` is only the base configuration, so a target-level definition shadowed it and the guard compared the user's declared intent against itself — matching every time. It is now `FLUTTER_STAGED_BUILD_MODE`, which nothing else writes. Also, being cheap next to the above: the phase records `FLUTTER_STAGED_SDK` and fails when a simulator payload is built for a device, the unrecognized- configuration path announces itself at `warning:` rather than `note:` (which xcbeautify and most CI filters drop) and gained an explicit opt-out, and the suite gained the cases that were missing — Debug with a release or profile payload, the Profile configuration, an unset CONFIGURATION, a stale marker over a JIT payload, and SDK mismatch. Deleting the Profile arm or the payload check now fails tests; both previously passed everything. 396 tests green, analyzer clean.
The new payload check failed a profile build that should have passed, and it was right to: `build/tvos/` is shared across modes and nothing there removes `kernel_blob.bin`, which only a debug build writes. `copyFlutterAssetsTree` mirrors that directory faithfully, so a profile or release build run after any debug build staged a 43 MB stale debug kernel and shipped it inside the AOT app. The engine ignores it there, which is why this went unnoticed — it is dead weight in the bundle rather than a crash, and it hands out the app's Dart kernel with a release build. Dropped at staging time rather than by cleaning the shared output directory, which incremental builds rely on.
Extracted from _copyFlutterAssets so the behaviour is testable without constructing a build: the removal is what keeps the new payload check from failing legitimate AOT builds, so it should not rest on an e2e run noticing.
Every guard test injects FLUTTER_STAGED_BUILD_MODE straight into the process
environment, so all of them stay green even when the chain that delivers it in a
real build is broken:
Generated.xcconfig
-> #include'd by Flutter/Debug.xcconfig and Flutter/Release.xcconfig
-> baseConfigurationReference on each configuration of the Runner target
Drop the include, or add a configuration without a base reference, and the phase
sees an unset marker — a hard failure for release, a warning for debug — with
nothing failing until someone archives.
The xcconfig body moved into `buildModeXcconfig` so the include is assertable at
all. The pbxproj half walks the Runner *target*'s configuration list (the
project-level configurations carry no base xcconfig, by design), requires a
baseConfigurationReference on each, and resolves it to one of the two Flutter
xcconfigs rather than to any file.
Verified against mutants: deleting a baseConfigurationReference and repointing
one at an unrelated file both fail these tests, and neither did before.
401 tests, analyzer clean.
Release 1.7.0 — Flutter 3.47.1 and the archive-mode guard
A parallel line for clients pinned to 3.32.8, branched from current dev so it carries the archive-mode guard and the rest of #66 rather than an older base. Not intended to merge into dev: dev is on 3.47.1, and merging this would move the toolchain backwards. The CLI is written against a newer flutter_tools than 3.32.8 ships, so pinning the version alone left it uncompilable — 18 errors. Resolved in the direction of what 3.32.8 actually provides: - Record-use (new in 3.47.0): `KernelSnapshot.recordedUsesFileName`, `recordedUsesEmptyContent` and `FeatureFlags.isRecordUseEnabled` do not exist, and `KernelSnapshot.outputs` never declares that file here, so the mirroring block has nothing to satisfy. - `copyAssets` predates `dartHookResult`, and `LinkHooks` / `DartHooksResult` do not exist. Dropped — we already skip the native-assets targets for tvOS and write an empty manifest ourselves, so there were never hook results. - `src/ios/lldb.dart` does not exist yet, so the lldb JIT attach (Path 1 of the device debug flow) cannot be built. Removed it, along with the now-dead `_findAppPid`, and fall through to the Xcode debugger — already implemented here as Path 2, and the same mechanism stock Flutter uses for iOS Core Devices. - `Device.isSupported` is synchronous here, not `Future<bool>`. - `UpdatePackagesCommand` takes no constructor arguments here. - `ArtifactSet` has no `displayName`, so that override could never be called. Precache prints the bare stamp name instead — cosmetic. Three tests asserted newer-SDK specifics: the record-use group (the feature is absent), the `informative` development artifact, and `writePackageConfigFiles`, which is `writePackageConfigFile` here. Verified: creates a project, builds it for the simulator in both debug and release, the generated project carries the "Check Flutter build mode" phase, `dart analyze` exits clean, 398/399 tests pass. The one failing test resolves a deliberately non-existent .local hostname and expects NXDOMAIN; it fails only on networks that hijack .local lookups. Device debug on a physical Apple TV is not available on this line — the LLDB helper is absent above, and the published v1.0.1-flutter3.32.8 engine aborts at VM init on tvOS 26. Simulator and release/profile are unaffected.
README gains the release-line banner and the 3.32.8 version block, and its on-device-debug note now describes the Xcode path this line actually uses — 3.32.8's flutter_tools has no direct-lldb attach API. The paragraph explaining that engine artifacts are named by commit is scoped to the mainline, since this line keeps its original version-shaped tag. CHANGELOG gains an entry for the line, starting from the mainline's current text rather than the 1.5.0-era copy. test.yml only fired on main and dev, so `release/**` lines have never had CI. Added them to both triggers.
The existing release/flutter-3.32.8 branch carries the same port done on an older base (1.5.0 era, 17 commits behind dev). This branch redoes it on current dev instead, so the line picks up the archive-mode guard from #66 and the rest of the mainline work rather than a three-week-old snapshot. Its documentation is not discarded: the release-line banner was carried over in the previous commit, rebased onto the mainline's current README. Its code changes are superseded by the equivalent ones here — the two ports converged independently on the same shape. Recording the merge so the line's history is continuous and the diff shows only what actually changes.
tool_revision returns the git HEAD SHA in a checkout, and the stamp holds the revision from the last successful compile. HEAD does not move for an uncommitted edit, so the two match and the old snapshot is reused silently — editing the tool and re-running it executes the previous build. Always hashing instead costs ~0.6s per invocation (52 files under bin/ and lib/), which is why the SHA shortcut exists. An mtime probe answers the same question in ~9ms: -print -quit stops at the first file newer than the stamp. Same shape as the pubspec.yaml -nt check directly above it. Verified: warm run does not recompile, touching lib/ makes the next run recompile, the run after does not. Unpatched, the same edit recompiles nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refreshes the 3.32.8 line onto current
dev.release/flutter-3.32.8is 17 commits behinddev— it branches from3e135ec, before the 1.6.0/1.7.0 work. Most importantly it predates #66, so a project generated from it has no "Check Flutter build mode" phase and can silently archive a debug engine into a release build: installs fine from Xcode, blank screen from TestFlight. That is the defect this line most needs, and it is exactly the kind of thing a client-pinned line should not be missing.This branch redoes the port on top of
dev(3c0231a) instead.Convergence with the existing port
Worth saying up front: I did the port independently before finding
release/flutter-3.32.8, and it landed on the same shape — same lldb removal (import, fields,_lldbAttachTimeout), same synchronousisSupported, sameverboseHelpdrop, sametvos_cache.dartandtvos_precache_test/tvos_shader_bundle_testadjustments. Two independent passes agreeing is reassuring about the approach.What is new relative to that port
devmoved to 3.47.0/3.47.1 in between, which added two things that do not exist in 3.32.8:KernelSnapshot.recordedUsesFileName,recordedUsesEmptyContentandFeatureFlags.isRecordUseEnabledare all 3.47.0.KernelSnapshot.outputsnever declaresrecorded_uses.jsonhere, so the mirroring block has nothing to satisfy — removed, along with its test group.DartBuild→LinkHooks. Both are gone here;copyAssetspredatesdartHookResultentirely. We already skip the native-assets targets for tvOS and write an empty manifest ourselves, so there were never hook results to pass on.Also cleaned up what the removals stranded:
_findAppPid(only the lldb path called it), three unused imports, and theArtifactSet.displayNameoverride, which cannot be called here because the member does not exist — precache prints the bare stamp name instead, cosmetic only.Verified
flutter-tvos --version→ Flutter 3.32.8 • Dart 3.8.1create --platforms=tvos→ project generated, and it carries theCheck Flutter build modephase withFLUTTER_STAGED_BUILD_MODEwritten correctlybuild tvos --simulator --debug→ Xcode build done, 22.0sbuild tvos --simulator --release→ Xcode build done, 18.9s (AOT + icon tree-shake)dart analyze lib/→ exits 0, no errors or warningsflutter test→ 398/399The one failure is
tvos_device_test.dart"rejects a coredevice name that does not resolve": it resolves a deliberately non-existent.localname and expects NXDOMAIN. On my network that name resolves to a real address (.locallookups are hijacked), so the gate correctly reports the host as resolvable. It should pass on CI.Open question: is on-device debug actually available on this line?
The README on
release/flutter-3.32.8states that debug on a physical Apple TV works, via Xcode's debug action over OSA scripting rather than lldb. I can confirm the mechanism exists and is what this branch falls through to.What I cannot confirm is the engine side.
fluttertv/engine's README says the publishedv1.0.1-flutter3.32.8debug artifact carries both the RWX marker and Dart'sMust run with --write-protect-code, and aborts at VM init on tvOS 26. I checked the shipped binary and both strings are present, andCheckIfNeedDebuggerHelpWithRX()doesFATALwhen the flag is off on a new enough OS — which is the state01-dart-globals.patchcreates. But I could not locateIsAtLeastIOS18_4()'s definition in the pinned Dart tree, so whether it returns true on tvOS is unverified by me, and I have no Apple TV to test on.The two claims may simply be from different times — the release-line README predates the RWX finding. Worth settling on hardware before this line ships to anyone who needs on-device debug. Separately,
flutter3.32.8/patches/has no!defined(DART_HOST_OS_TVOS)exclusion whileflutter3.44.8/patches/does, which is worth its own look.Documentation and CI
README starts from the mainline's current text and gains the release-line banner, so the line is documented at 1.7.0 rather than reverting to the 1.5.0-era copy. Its on-device-debug note now describes the Xcode path this line actually uses, and the paragraph about naming engine artifacts by commit is scoped to the mainline, since this line keeps its original version-shaped tag. CHANGELOG gains a
1.7.0-flutter3.32.8entry.test.ymlfired only onmainanddev, sorelease/**lines have never had CI — the existing 3.32.8 branch was never verified by anything but hand. Both triggers now includerelease/**.The merge with
release/flutter-3.32.8is recorded with-s ours: its documentation was carried over by hand (rebased onto current text) and its code changes are superseded by the equivalent ones here, so the line's history stays continuous and the diff shows only what actually changes.