Skip to content

Release 1.4.0: origin-signed engine (ITMS-91065) + App Store blockers + pub workspace support#35

Open
MAUstaoglu wants to merge 10 commits into
mainfrom
dev-fixes
Open

Release 1.4.0: origin-signed engine (ITMS-91065) + App Store blockers + pub workspace support#35
MAUstaoglu wants to merge 10 commits into
mainfrom
dev-fixes

Conversation

@MAUstaoglu

@MAUstaoglu MAUstaoglu commented Jul 11, 2026

Copy link
Copy Markdown
Member

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)

  • The tvOS engine artifact now ships origin-signed (Developer ID Application, hardened runtime + secure timestamp), the same way flutter.dev signs its iOS artifacts. Apps embedding an unsigned engine were rejected with ITMS-91065 ("Missing signature") only at external Beta App Review / App Store review — an app-identity re-sign at export does not satisfy the check; the artifact itself must carry the origin signature.
  • bin/internal/engine.versionv1.0.1-flutter3.44.5 (same Flutter 3.44.5 build, re-published signed).
  • Engine tooling (separate PR in fluttertv/engine): build.sh --signing-identity + a verify_artifacts.sh origin-sign gate.

Fixed

Added

  • Build-time migration warning for existing projects whose app-icon catalog predates the completed template.
  • A "Sign Flutter.framework" build phase (re-signs the SPM-embedded engine to the app's own team for device installs).

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 the FLUTTER_ROOT / flutter_export_environment.sh generation.

Verified end-to-end: a clean flutter-tvos build tvos --release produces App.framework with minos 13.0, no AOT leakage, and consumes the origin-signed engine (verify_artifacts.sh green, authority = Developer ID Application → Apple Root CA).

Closes #29

thachnb-harman and others added 10 commits July 10, 2026 11:47
#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).
@MAUstaoglu MAUstaoglu requested a review from DenisovAV July 11, 2026 07:00

@DenisovAV DenisovAV left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.5 exists, and codesign -dvvv on the downloaded release shows Authority=Developer ID Application: MEHMET ALI USTAOGLU (866PPL96Z4), flags=0x10000(runtime) (hardened runtime), and a secure timestamp — on both the bare Flutter.framework and the tvos-arm64 xcframework slice. All 6 asset names match _artifactZipNames exactly, so precache won'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.build line-by-line against upstream CopyFlutterBundle.build — byte-identical apart from TargetPlatform.android.ios. The if (buildMode == BuildMode.debug) gate that copies kernel_blob.bin/vm_snapshot_data/isolate_snapshot_data is preserved verbatim, and the new skip list is guarded by entity is Directory, so those files can't be dropped. Debug/JIT is safe.
  • The shader fix is load-bearing and correct. _shaderTargetsFromTargetPlatform gives android[--sksl, --runtime-stage-gles, --runtime-stage-gles3, --runtime-stage-vulkan] (no Metal) and ios[--runtime-stage-metal]. copyAssets threads 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. sdkName is 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 bad minos). _kTvosMinimumOSVersion feeds both the clang flag and App.framework's MinimumOSVersion, 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 codesign phase does not swallow failures (codesign is the last command in the else, so its exit code is the phase's), the simulator path skips correctly, and --timestamp=none is 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/tvostvos_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)

  1. COCOAPODS_PARALLEL_CODE_SIGN is written only to the .sh, where it does nothing — and the new test pins that. Inline.
  2. flutter-tvos clean doesn't remove flutter_export_environment.sh. lib/commands/clean.dart:28 cleans Generated.xcconfig but not its new sibling — and this is the PR that starts writing that file. A stale script with an absolute FLUTTER_ROOT survives a clean; a user who then moves the project and builds from Xcode gets cargokit sourcing a dead SDK path. tvos_clean_test.dart already exists and already asserts on Generated.xcconfig, so it's two lines total:
    _cleanFile(tvosDir, 'Flutter/flutter_export_environment.sh');
  3. The sign phase's not-found branch is a warning on the one build where it's fatal, and ${PRODUCT_NAME}.app is the wrong path. Inline.
  4. appIconCatalogNeedsMigration has a false negative on the exact remediation path its own warning recommends. Inline.
  5. The shader fix — the headline #34 item — has no test. TvosCopyFlutterBundle.build is 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 asserts copyAssets is called with targetPlatform: TargetPlatform.ios. A FakeProcessManager/spy assertion on that one argument would guard the whole thing. Same shape for tvosVersionMinFlag: the argv-builder statics are well tested, but nothing asserts the production call sites pass the flag — you could pass '' at application.dart and every new test stays green.

Minor

  • lib/tvos_plugins.dart keeps a hand-rolled upward walk where flutter_tools already ships one — and this repo already uses it: tvos_builder.dart:79findPackageConfigFileOrDefault(project.directory). Inline.
  • CHANGELOG references engine/build.sh --signing-identity and engine/verify_artifacts.sh, but there's no engine/ directory in this repo — those live in fluttertv/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"'))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:50Generated.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";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/tvos_plugins.dart
.childDirectory('.dart_tool')
.childFile('package_config.json');
Directory packageConfigSearchDir = project.directory;
while (!packageConfigFile.existsSync() &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:79findPackageConfigFileOrDefault(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:

  1. Upstream normalizes/absolutizes first (fileSystem.path.normalize(dir.absolute.path)). This version walks project.directory as-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 because project.directory is always absolute, but that's a property of the callers, not of this code.
  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin discovery does not support Dart pub workspaces

3 participants