Release 1.4.0: origin-signed engine (ITMS-91065) + App Store blockers + pub workspace support#35
Release 1.4.0: origin-signed engine (ITMS-91065) + App Store blockers + pub workspace support#35MAUstaoglu wants to merge 10 commits into
Conversation
#32) Plugin discovery resolved plugin paths through <project>/.dart_tool/package_config.json only. Pub workspaces hoist that file to the workspace root, so for any workspace-member app the map came back empty and every flutter.plugin.platforms.tvos plugin was silently dropped: empty GeneratedPluginRegistrant, no pods or SPM packages, and MissingPluginException for every plugin call at runtime. Walk up from the project directory until a .dart_tool/package_config.json is found (the same resolution pub itself uses), and resolve relative rootUri entries against the found file's own directory instead of assuming the project-local location.
…ipt phases (#33) Upstream iOS/macOS builds write FLUTTER_ROOT into Generated.xcconfig and generate flutter_export_environment.sh next to it. The tvOS build did neither, so native-build tooling that runs inside CocoaPods script phases and sources that file to locate the Dart SDK (e.g. cargokit, used by Rust-based FFI plugins) fails with "dart: command not found". Write FLUTTER_ROOT into Generated.xcconfig and generate tvos/Flutter/flutter_export_environment.sh with the same variables the upstream platforms export.
The tvOS engine is vended as a SwiftPM .binaryTarget on the dynamic
Flutter.xcframework, pulled into the app bundle transitively through the
static FlutterGeneratedPluginSwiftPackage umbrella. Xcode embeds that
framework into Runner.app/Frameworks/ but does not code-sign it (it is not
an explicit "Embed & Sign" product on the Runner target), so archives ship
an unsigned Flutter.framework.
This installs locally and via Internal TestFlight (App Store Connect
re-signs the bundle during processing) but Beta App Review / the App Store
reject it with ITMS-91065 ("Missing signature"), because Flutter is a
commonly-used third-party SDK that must ship signed. (The privacy-manifest
half, ITMS-91061, is already satisfied.)
We deliberately do not sign the engine artifact — flutter-tvos is public, so
signing the zip would leak a key or stamp every app with the maintainer's
team. Instead the app template gains a "Sign Flutter.framework" run-script
build phase (last phase, so it runs after Xcode's embed) that re-signs the
embedded engine with EXPANDED_CODE_SIGN_IDENTITY — the app's own identity,
exactly what CocoaPods' embed script did. It no-ops on the simulator and runs
for build, run, AND archive.
Verified against a real arm64 build of the example: Xcode embeds the
framework, then the Sign phase runs afterward.
Existing projects must regenerate (flutter-tvos create .) to gain the phase;
_warnIfFlutterFrameworkUnsigned prints a build-time warning for device builds
whose Runner project lacks it.
TvosCopyFlutterBundle inherited upstream CopyFlutterBundle.build, which hard-codes targetPlatform: TargetPlatform.android for copyAssets. The target platform drives impellerc's runtime-stage flags, so every fragment shader was bundled with SkSL/GLES/Vulkan stages only — and the tvOS engine renders with Impeller/Metal, so all shaders failed at runtime with: Exception: Asset '....frag' does not contain appropriate runtime stage data for current backend (Metal). Found stages: SkSL OpenGLES OpenGLES3 Vulkan Upstream can hard-code android because on real iOS the release asset copy happens in ReleaseIosApplicationBundle, which passes ios — but tvOS bundles assets through CopyFlutterBundle for every build mode. Mirror upstream CopyFlutterBundle.build in TvosCopyFlutterBundle (same maintenance contract as TvosKernelSnapshot: re-mirror on Flutter upgrades) with targetPlatform: TargetPlatform.ios, so shaders get the Metal runtime stage. This also stops requesting SkSL, which some shaders (e.g. ones using fwidth or array initializers) can never compile to anyway. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…sets AOT-intermediate leak: gen_snapshot's snapshot_assembly.S/.o were written to environment.outputDir (build/tvos/), which copyFlutterAssetsTree mirrors into the app's flutter_assets — shipping stray .S/.o files that App Store validation rejects. Write them to buildDir (.dart_tool) instead, and extend the asset-copy skip list (Profile-, aot) so a stale build/tvos/aot/ from an older CLI can't leak either. App.framework deployment target (ITMS-90208): the AOT clang assemble+link steps omitted the min-version flag, so LC_BUILD_VERSION minos was stamped with the SDK version (26.0) instead of MinimumOSVersion (13.0), failing validation. Add tvosVersionMinFlag (device vs simulator) to both clang calls. Template asset catalog: the app template's tvOS brand assets lacked @2x layer images and the Top Shelf Image Wide asset, so generated projects failed App Store validation. Complete the catalog (1x+2x for every layer, standard + wide top shelf) with the validated Contents.json structure. Also reword the "Sign Flutter.framework" phase comments: it re-signs for device installs and does NOT fix ITMS-91065 (the origin-signed engine does). Regression tests cover all three in tvos_app_bundle_test.dart.
…the fix The completed asset catalog (@2x layers + Top Shelf Wide) only reaches new projects — AppIcon.brandassets is copied once at `create` time and never regenerated on build, since it holds the user's own icon art. So a project created before the catalog fix keeps an incomplete catalog and still fails App Store asset validation even when rebuilt with an up-to-date CLI. The build can't repair it without clobbering custom icons, so add a device- build migration warning (same pattern as _warnIfFlutterFrameworkUnsigned): detect the old catalog by the absence of the `top-shelf-image-wide` role in the brand-assets index and point the user at regeneration or a by-hand fix. Detection is a testable static (appIconCatalogNeedsMigration) with coverage for old / completed / absent catalogs.
The fix for workspace plugin discovery (caa15bd) shipped without a test. Add a regression test that models a real pub workspace: the tvOS app is a workspace member whose own .dart_tool holds only workspace_ref.json, while package_config.json is hoisted to the workspace root with relative rootUris. discoverTvosSpmPlugins must still find the member's federated tvOS plugin. Verified this test fails on the pre-fix code (main): discovery returns an empty list because the naive single-directory read of the member's .dart_tool/package_config.json finds nothing — exactly the silent "no plugins registered" symptom reported in the issue.
Bump version to 1.3.4 and add the changelog entry covering the fixes since 1.3.3: pub-workspace plugin discovery (#29/#32), pod script env (#33), the App Store validation blockers (AOT-intermediate leak, App.framework min-version / ITMS-90208, complete template asset catalog), the app-icon migration warning, and the device-install "Sign Flutter.framework" phase. Engine unchanged (Flutter 3.44.5).
The #33 fix (FLUTTER_ROOT in Generated.xcconfig + a generated flutter_export_environment.sh so pod script phases like cargokit can locate the Dart SDK) shipped with no tests. Extract both file bodies into testable statics (buildGeneratedXcconfig, buildFlutterExportEnvironment) and assert FLUTTER_ROOT and the exported variables. Also extract the two AOT clang invocations into statics (aotAssembleArgs, aotLinkArgs) and assert both carry the -mtvos-version-min flag — previously only the flag's value was unit-tested; that it reaches both the assemble and link argv was proven solely by an end-to-end build. No behavior change.
Retarget the release to 1.4.0 and point at the origin-signed engine: - bin/internal/engine.version → v1.0.1-flutter3.44.5 (same 3.44.5 build, re-published with Flutter.framework / Flutter.xcframework origin-signed so apps clear Apple's commonly-used-SDK check, ITMS-91065). - CHANGELOG: fold the 1.3.4 fixes into 1.4.0 and add the engine/signing section. - README: refresh the Current version block (1.4.0, Flutter 3.44.5, engine v1.0.1, origin-signed).
DenisovAV
left a comment
There was a problem hiding this comment.
Reviewed this against the real artifacts and the real code — including downloading tvos_release_arm64.zip and running codesign on it, and compiling with the min-version flags to check the resulting Mach-O. The headline claims all hold up. A few things to fix, and one pre-existing bug that I think should block the tag.
Verified — these are genuinely done
- The engine artifact really is origin-signed.
v1.0.1-flutter3.44.5exists, andcodesign -dvvvon the downloaded release showsAuthority=Developer ID Application: MEHMET ALI USTAOGLU (866PPL96Z4),flags=0x10000(runtime)(hardened runtime), and a secure timestamp — on both the bareFlutter.frameworkand thetvos-arm64xcframework slice. All 6 asset names match_artifactZipNamesexactly, soprecachewon't break on merge. ITMS-91065 is actually addressed, not just plausibly. - The AOT-intermediate fix does not over-exclude. I diffed the hand-mirrored
TvosCopyFlutterBundle.buildline-by-line against upstreamCopyFlutterBundle.build— byte-identical apart fromTargetPlatform.android→.ios. Theif (buildMode == BuildMode.debug)gate that copieskernel_blob.bin/vm_snapshot_data/isolate_snapshot_datais preserved verbatim, and the new skip list is guarded byentity is Directory, so those files can't be dropped. Debug/JIT is safe. - The shader fix is load-bearing and correct.
_shaderTargetsFromTargetPlatformgivesandroid→[--sksl, --runtime-stage-gles, --runtime-stage-gles3, --runtime-stage-vulkan](no Metal) andios→[--runtime-stage-metal].copyAssetsthreads it straight to impellerc. Right value, and it matches what the rest of the repo already uses for tvOS (tvos_device.dart:372,tvos_builder.dart:44). - ITMS-90208 can't silently regress.
sdkNameis effectively an enum (tvos_build_info.dart:18→'appletvos'|'appletvsimulator'), so the simulator-flag-on-device direction is unreachable, and an unknown value falls back to the device flag (fails loudly at link rather than shipping a badminos)._kTvosMinimumOSVersionfeeds both the clang flag andApp.framework'sMinimumOSVersion, so the two values ITMS-90208 compares match by construction. Confirmed empirically: no flag →minos 26.5(the bug); with the flag →minos 13.0. - The
codesignphase does not swallow failures (codesignis the last command in theelse, so its exit code is the phase's), the simulator path skips correctly, and--timestamp=noneis upstream's own convention for embedded-framework re-signing.
Also: the code comments are honest that the re-sign phase does not by itself satisfy ITMS-91065. That distinction trips people up constantly, and getting it right in the comments is worth calling out.
🔴 The one I'd fix before tagging — clean release builds ship with no flutter_assets
lib/build_targets/application.dart:855. Pre-existing (since 1.0.0), not introduced here — but it's three lines from code this PR edits, and it's precisely the "green build, rejected/broken later" class this release exists to eliminate.
if (tvosOutputDir.childFile('kernel_blob.bin').existsSync()) {
flutterAssetsSource = tvosOutputDir; // build/tvos/
} else if (defaultDir.existsSync()) {
flutterAssetsSource = defaultDir; // build/flutter_assets/ — never written for tvOS
}
...
} else {
globals.logger.printTrace('flutter_assets not found in build output, skipping.');
}kernel_blob.bin is written only in debug (upstream, and this PR's mirror preserves that). In release, copyAssets still fills build/tvos/ with AssetManifest.bin, fonts/, assets/, shaders/ — but there's no kernel_blob.bin, so the probe is false. The fallback checks build/flutter_assets/, which the tvOS build never writes (outputDir is build/tvos — tvos_builder.dart:39). So flutterAssetsSource == null, the copy is skipped, and the only trace of it is a printTrace.
Failure scenario: rm -rf build && flutter-tvos build tvos --release (i.e. any fresh clone or CI runner). tvos/Flutter/flutter_assets is never created, the Xcode phase prints warning: flutter_assets not found, the build goes green, and the archive ships with zero assets — MaterialIcons boxes, Unable to load asset, blank screens at runtime.
Why it hasn't bitten: anyone who ran a debug build first leaves a stale build/tvos/kernel_blob.bin behind, the probe passes, and the freshly-written release assets get copied. Invisible on a dev machine, fatal on clean CI.
Probe for something that exists in every mode, and make the miss fatal — there is no legitimate build in which a Flutter app has no assets:
if (tvosOutputDir.childFile('AssetManifest.bin').existsSync()) {
flutterAssetsSource = tvosOutputDir;
} else if (defaultDir.existsSync()) {
flutterAssetsSource = defaultDir;
} else {
throwToolExit(
'No Flutter assets found in ${tvosOutputDir.path}. The asset bundling step did '
'not run. Try "flutter-tvos clean" and rebuild; if it persists, file an issue.',
);
}Also worth fixing (details inline)
COCOAPODS_PARALLEL_CODE_SIGNis written only to the.sh, where it does nothing — and the new test pins that. Inline.flutter-tvos cleandoesn't removeflutter_export_environment.sh.lib/commands/clean.dart:28cleansGenerated.xcconfigbut not its new sibling — and this is the PR that starts writing that file. A stale script with an absoluteFLUTTER_ROOTsurvives a clean; a user who then moves the project and builds from Xcode gets cargokit sourcing a dead SDK path.tvos_clean_test.dartalready exists and already asserts onGenerated.xcconfig, so it's two lines total:_cleanFile(tvosDir, 'Flutter/flutter_export_environment.sh');
- The sign phase's not-found branch is a warning on the one build where it's fatal, and
${PRODUCT_NAME}.appis the wrong path. Inline. appIconCatalogNeedsMigrationhas a false negative on the exact remediation path its own warning recommends. Inline.- The shader fix — the headline #34 item — has no test.
TvosCopyFlutterBundle.buildis the most fragile code in the PR (a hand-mirror of upstream that must be re-synced on every Flutter upgrade, as its own comment says), and nothing assertscopyAssetsis called withtargetPlatform: TargetPlatform.ios. AFakeProcessManager/spy assertion on that one argument would guard the whole thing. Same shape fortvosVersionMinFlag: the argv-builder statics are well tested, but nothing asserts the production call sites pass the flag — you could pass''atapplication.dartand every new test stays green.
Minor
lib/tvos_plugins.dartkeeps a hand-rolled upward walk whereflutter_toolsalready ships one — and this repo already uses it:tvos_builder.dart:79→findPackageConfigFileOrDefault(project.directory). Inline.- CHANGELOG references
engine/build.sh --signing-identityandengine/verify_artifacts.sh, but there's noengine/directory in this repo — those live influttertv/engine. Worth annotating so readers don't hunt for them here.
Nothing here undermines the release: the signing, the shader fix, the min-version fix and the AOT fix are all real and verified. But every new guard in this PR is advisory (printWarning / printTrace), on a release whose whole purpose is eliminating failures that only surface days later at review — and three of them can report "looks fine" on a project that will be rejected. The application.dart:855 probe is the one I'd genuinely hold the tag for.
| ..writeln('export "FLUTTER_BUILD_DIR=$buildDir"') | ||
| ..writeln('export "FLUTTER_BUILD_NAME=$buildName"') | ||
| ..writeln('export "FLUTTER_BUILD_NUMBER=$buildNumber"') | ||
| ..writeln('export "COCOAPODS_PARALLEL_CODE_SIGN=true"')) |
There was a problem hiding this comment.
COCOAPODS_PARALLEL_CODE_SIGN=true is written only into flutter_export_environment.sh, so as shipped it does nothing.
It's consumed by CocoaPods' [CP] Embed Pods Frameworks phase as an Xcode build setting — that phase never sources this script. cargokit ignores it too. Upstream builds a single xcodeBuildSettings list and feeds it to both writers (xcode_build_settings.dart:50 → Generated.xcconfig, :56 → the .sh, emitting export "$line" per entry), adding this var at :185. So upstream's .sh is a strict subset of the xcconfig. Here it's a strict superset — the var exists only where it has no effect.
The tvOS Podfile uses use_frameworks!, so the embed phase does exist and would benefit. One line in buildGeneratedXcconfig:
..writeln('COCOAPODS_PARALLEL_CODE_SIGN=true')And the new test currently pins the bug: tvos_app_bundle_test.dart asserts expect(sh, contains('export "COCOAPODS_PARALLEL_CODE_SIGN=true"')), while the companion xcconfig test asserts only the six FLUTTER_* vars and says nothing about this one. Net: a test fails if you remove it from the .sh, and no test ever fails for its absence from the xcconfig — so a future fixer has to argue with the suite.
The parity assertion that would actually catch this is the upstream invariant, in this direction: every non-conditional export "K=V" in the .sh must appear as K=V in the xcconfig. (I'd originally suggested the reverse — that one passes today and would have missed it.) Better still, derive both strings from one settings list, the way upstream does, so they can't drift at all.
| ); | ||
| runOnlyForDeploymentPostprocessing = 0; | ||
| shellPath = /bin/sh; | ||
| shellScript = "#!/bin/sh\n# Code-sign the Flutter engine embedded via the FlutterFramework Swift package.\n# Xcode auto-embeds the dynamic Flutter.framework that is pulled in transitively\n# through the static FlutterGeneratedPluginSwiftPackage umbrella, but it does NOT\n# code-sign it (Flutter.framework is not an explicit \"Embed & Sign\" product on this\n# target). The engine artifact ships ORIGIN-signed by flutter-tvos — that origin\n# signature is what satisfies App Store review's ITMS-91065 commonly-used-SDK\n# check (an app-identity re-sign does not). Re-sign the embedded copy here with\n# the app's own identity, the same way CocoaPods' embed script did, so device\n# installs don't carry nested code signed by a foreign team. Runs for build,\n# run, AND archive.\nFRAMEWORK=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Frameworks/Flutter.framework\"\nif [ ! -d \"${FRAMEWORK}\" ]; then\n echo \"warning: Flutter.framework not present at ${FRAMEWORK}; nothing to sign.\"\nelif [ \"${CODE_SIGNING_REQUIRED}\" = \"NO\" ] || [ -z \"${EXPANDED_CODE_SIGN_IDENTITY}\" ]; then\n echo \"Skipping Flutter.framework signing (code signing not required for this build).\"\nelse\n echo \"Codesigning Flutter.framework with ${EXPANDED_CODE_SIGN_IDENTITY}...\"\n codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --timestamp=none --generate-entitlement-der \"${FRAMEWORK}\"\nfi\n"; |
There was a problem hiding this comment.
Two problems in this branch, both of which end in "green build, unsigned framework".
1. ${PRODUCT_NAME}.app is the wrong path. The bundle directory is ${WRAPPER_NAME}; the thing to sign is ${CODESIGNING_FOLDER_PATH}. PRODUCT_NAME happens to equal the wrapper base name in the default template, but any target that overrides it (custom product name, flavors, $(inherited) games) resolves FRAMEWORK to a path that doesn't exist.
2. That miss is a warning: with exit 0. On a build where CODE_SIGNING_REQUIRED != NO and EXPANDED_CODE_SIGN_IDENTITY is non-empty, a missing Flutter.framework isn't "nothing to do" — it means the SPM embed didn't happen (or landed elsewhere), and the app ships a nested framework signed by a foreign team. The user finds out at device install (0xe8008014) or at review.
It's also worth noting the Dart-side guard doesn't catch it either: _warnIfFlutterFrameworkUnsigned greps the pbxproj for the literal string 'Sign Flutter.framework', so it reports "fine" whenever the phase exists — including when the phase silently no-op'd. Both guards say OK while nothing was signed. (Consider renaming it _warnIfProjectLacksFlutterSigningPhase — it checks project config, not a signature.)
Reordering the branches so the "do we need to sign at all" check comes first keeps the simulator skipping silently and makes only signing builds hard-fail:
FRAMEWORK="${CODESIGNING_FOLDER_PATH}/Frameworks/Flutter.framework"
if [ "${CODE_SIGNING_REQUIRED}" = "NO" ] || [ -z "${EXPANDED_CODE_SIGN_IDENTITY}" ]; then
echo "Skipping Flutter.framework signing (code signing not required for this build)."
elif [ ! -d "${FRAMEWORK}" ]; then
echo "error: Flutter.framework was not embedded at ${FRAMEWORK}. Regenerate the tvOS project."
exit 1
else
codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --timestamp=none --generate-entitlement-der "${FRAMEWORK}"
fi(The pre-existing "Embed App.framework" phase makes the same ${PRODUCT_NAME}.app assumption — worth fixing in the same pass, though it's not new here.)
| if (!index.existsSync()) { | ||
| return false; // No stock catalog to validate. | ||
| } | ||
| return !index.readAsStringSync().contains('top-shelf-image-wide'); |
There was a problem hiding this comment.
This check has a false negative on exactly the path its own warning tells users to take.
It reads only the top-level Contents.json and only looks for the top-shelf-image-wide role string. It never verifies that the six @2x layer images exist — even though the warning text says the catalog is missing "the @2x icon layers and the Top Shelf Image Wide asset", and the @2x layers are half the rejection cause.
Scenario: a user with custom icons follows the advice to fix the catalog by hand. They do the easy, visible half — paste the top-shelf-image-wide entry into Contents.json — and don't get around to producing six @2x PNGs. Next build: no warning, because the marker string is now present. They archive, upload, and get rejected by asset validation again — with the tool now actively telling them the catalog is fine. A migration warning that goes quiet when you do half the migration is worse than no warning.
Making it structural also gives you a much better message (list exactly what's missing, so it stays actionable and stays on until the migration is actually complete):
@visibleForTesting
static List<String> missingAppIconAssets(Directory tvosProjectDir) {
final Directory brand = tvosProjectDir
.childDirectory('Runner').childDirectory('Assets.xcassets')
.childDirectory('AppIcon.brandassets');
if (!brand.childFile('Contents.json').existsSync()) return const <String>[];
final missing = <String>[];
if (!brand.childFile('Contents.json').readAsStringSync().contains('top-shelf-image-wide')) {
missing.add('the "top-shelf-image-wide" role in AppIcon.brandassets/Contents.json');
}
for (final stack in const <String>['Small', 'Large']) {
for (final layer in const <String>['Back', 'Middle', 'Front']) {
final File f = brand
.childDirectory('App Icon - $stack.imagestack')
.childDirectory('$layer.imagestacklayer')
.childDirectory('Content.imageset')
.childFile('${stack.toLowerCase()}_${layer.toLowerCase()}@2x.png');
if (!f.existsSync()) missing.add(f.path);
}
}
return missing;
}Two smaller things in the same method: readAsStringSync() is uncaught (an unreadable catalog crashes the tool with a stack trace rather than a message), and the hard-coded Runner/ directory means a renamed target silently returns false.
Separately — severity. This fires at step 6b, then pod install and a multi-minute Xcode spinner scroll it away. There's no build ipa/archive command in the CLI, so the submission path is the user archiving from Xcode; this line is the only chance to reach them. For an incomplete catalog on a --release device build the outcome is deterministic and known (asset validation rejects the archive). At minimum re-emit both warnings after Xcode build done. as the last thing on screen; ideally throwToolExit for release+device. The message text itself is genuinely good — it's the severity and the placement that undercut it.
| .childDirectory('.dart_tool') | ||
| .childFile('package_config.json'); | ||
| Directory packageConfigSearchDir = project.directory; | ||
| while (!packageConfigFile.existsSync() && |
There was a problem hiding this comment.
This hand-rolls an upward walk that flutter_tools already ships as findPackageConfigFile / findPackageConfigFileOrDefault (dart/package_map.dart:25,48), exposed as FlutterProject.packageConfig (project.dart:227). Upstream's own findPlugins() — the direct analogue of this function — uses it (flutter_plugins.dart:109).
And this repo already calls it: lib/tvos_builder.dart:79 → findPackageConfigFileOrDefault(project.directory).path. So after this PR the build resolves package_config.json with upstream semantics in one place and hand-rolled semantics in another, in the same build. They can disagree — see below.
_walkPluginDependencies already takes a FlutterProject and project.dart is already imported, so this is a one-line swap with no new imports:
final File packageConfigFile = project.packageConfig;Two real differences you get for free:
- Upstream normalizes/absolutizes first (
fileSystem.path.normalize(dir.absolute.path)). This version walksproject.directoryas-is — and for a relative directory,path.dirname('.') == '.', so the termination condition trips on the first check and the walk silently never happens, reintroducing the very workspace bug this hunk exists to fix. It doesn't bite today becauseproject.directoryis always absolute, but that's a property of the callers, not of this code. - Upstream terminates on
fileSystem.path.equals(...), not a raw string!=(case/separator-insensitive on macOS/Windows).
Good news: the new tvos_workspace_plugin_discovery_test.dart drives the public discoverTvosSpmPlugins and asserts only on names and resolved paths — it doesn't pin the walk. So the swap stays green and the test keeps guarding the behavior.
Ships the last of the App Store submission blockers. The tvOS engine artifact is now origin-signed (published as
v1.0.1-flutter3.44.5), so downstream apps clear Apple's commonly-used-SDK signature check at external review.Engine / signing (ITMS-91065)
bin/internal/engine.version→v1.0.1-flutter3.44.5(same Flutter 3.44.5 build, re-published signed).fluttertv/engine):build.sh --signing-identity+ averify_artifacts.shorigin-sign gate.Fixed
TvosCopyFlutterBundlecopied assets asTargetPlatform.android, so every.fragshipped without the Metal runtime stage and failed at runtime. (fix: bundle fragment shaders with the Metal runtime stage #34)FLUTTER_ROOT+flutter_export_environment.shso cargokit/Rust-FFI tooling can locate the Dart SDK. (fix: write FLUTTER_ROOT and flutter_export_environment.sh for pod script phases #33)flutter_assets(App Store rejection).App.frameworkmin-version matchesMinimumOSVersion(13.0) — ITMS-90208.Added
Tests
286 passing. New regression coverage for pub-workspace discovery (verified failing on pre-fix
main), the AOT-intermediate leak, both AOT clang min-version calls, the template catalog, the migration warning, and theFLUTTER_ROOT/flutter_export_environment.shgeneration.Verified end-to-end: a clean
flutter-tvos build tvos --releaseproducesApp.frameworkwithminos 13.0, no AOT leakage, and consumes the origin-signed engine (verify_artifacts.shgreen, authority = Developer ID Application → Apple Root CA).Closes #29