Skip to content

[native assets] Respect the iOS deployment target in framework metadata - #194146

Draft
leehack wants to merge 2 commits into
flutter:masterfrom
leehack:fix/native-assets-ios-deployment-target
Draft

leehack wants to merge 2 commits into
flutter:masterfrom
leehack:fix/native-assets-ios-deployment-target

Conversation

@leehack

@leehack leehack commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

An app targeting iOS 16.4 can bundle a native asset binary requiring 16.4, while Flutter writes 15.0 into the generated framework's MinimumOSVersion. This PR forwards Xcode's IPHONEOS_DEPLOYMENT_TARGET to assemble and preserves the full version in native framework metadata.

Partially addresses #194145 (iOS framework packaging only). The broader issue stays open for hook configuration and macOS work. Related: #145104.

Upstream prerequisite: Full deployment-version propagation is proposed in dart-lang/native#3741 and draft dart-lang/native#3742. Once the API shape is agreed and released, Flutter can consume it and extend this PR to forward the complete iOS and macOS versions into build/link hooks, with consistent metadata and propagation/cache/default/error tests. The current Flutter patch remains limited to iOS framework metadata; the native API is not yet merged or released.

Missing/empty settings retain the existing 15.0 fallback. Settings below Flutter's iOS 15 baseline retain that floor. Major/minor/patch components are preserved; malformed input produces an actionable ToolExit before framework copying. The define participates in the existing build-environment hash, so changing the target selects a new build configuration.

Scope

  • Applies to native frameworks packaged through the iOS Xcode backend, for devices and simulators. Settings come from the app; dependency packages do not need platform folders.
  • Leaves compiler hook inputs unchanged. Both Apple hook version fields remain integer-valued; macOS packaging is unchanged.
  • Does not inspect a binary's deployment requirement, add package-level overrides, or replace companion-package privacy manifest packaging. No public API or website documentation change is needed for this metadata fix.

Validation

  • Verified the mismatch on stable 3.47.6 (5fc346839b) and master (a381eea6b1) using the reduced C-library reproducer in [native assets] Respect app deployment targets on iOS and macOS #194145. Five regression cases failed before the implementation.
  • On this refreshed head, 101 tests pass across the iOS, Xcode backend, shared native-assets/build-system, macOS, Android, Linux, and Windows test files. They cover debug/release metadata, two architectures, defaults/floor/major/minor/patch versions, trimming, malformed input, and build/prepare forwarding. macOS command expectations exclude the iOS define.
  • Complete flutter_tools analysis: no issues. Formatting and diff whitespace checks pass. Local tooling uses Dart 3.15.0-15.0.dev; Initial policy checks and Google CLA pass. Flutter test CI is awaiting a maintainer-applied CICD label.
  • With the original patch commit 2688125b880 and macOS 26.7.1 / Xcode 27.0: unsigned release device and debug simulator builds produce app/framework versions of 16.4, with binary minos 16.4 (both arm64/x86_64 simulator slices). Changing the app target to 17.2.1 without cleaning updates framework metadata to 17.2.1.
  • The refreshed base 71e4717a617 adds only Dart/Skia DEPS rolls and an unrelated OverlayPortal change; none of the affected Flutter Tools paths changed. The targeted tests and analysis were repeated after merging that base without rewriting the published branch.

No full Flutter suite, archive/App Store validation, device/simulator execution, or complete llamadart build without companions was performed. This remains a draft for design and contributor review.

export FLUTTER_ROOT="$PWD"
cd packages/flutter_tools
../../bin/cache/dart-sdk/bin/dart test \
  test/general.shard/isolated/ios/native_assets_test.dart \
  test/general.shard/xcode_backend_test.dart \
  test/general.shard/isolated/native_assets_test.dart \
  test/general.shard/isolated/build_system/targets/native_assets_test.dart \
  test/general.shard/isolated/macos/native_assets_test.dart \
  test/general.shard/isolated/android/native_assets_test.dart \
  test/general.shard/isolated/linux/native_assets_test.dart \
  test/general.shard/isolated/windows/native_assets_test.dart
../../bin/cache/dart-sdk/bin/dart analyze

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the AI contribution guidelines and understand my responsibilities, or I am not using AI tools.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant in-code documentation (doc comments with ///).
  • If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in flutter/website (or verified none is needed).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • I followed the breaking change policy and added Data Driven Fixes where supported.
  • All existing and new tests are passing. (Targeted tests pass; full Flutter CI is pending.)

If you need help, consider asking for advice on the #hackers-new channel on Discord.

If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions Bot added tool Affects the "flutter" command-line tool. See also t: labels. team-ios Owned by iOS platform team team-macos Owned by the macOS platform team labels Oct 10, 2026
@leehack
leehack marked this pull request as ready for review October 11, 2026 12:48
@leehack
leehack requested review from a team as code owners October 11, 2026 12:48

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces support for the iOS deployment target (IPHONEOS_DEPLOYMENT_TARGET) when packaging native code assets, passing it from Xcode and validating it against the minimum supported iOS version. Feedback on the changes highlights two compilation issues: an invalid syntax pattern (?deploymentTarget) in the test file and a type mismatch where a double is passed to the Version constructor instead of an int.

@dcharkes

Copy link
Copy Markdown
Contributor

I think we should aim for the version number that comes from the xcode build to flow into flutter assemble which should then flow properly into the build hooks (and the metadata). Only fixing the metadata makes it inconsistent.

(Maybe we should do MacOS and iOS in the same PR? Aren't their constraints rather similar?)

Here is an AI explanation on how to do this following a similar pattern as #181004 takes for wiring the ccompiler info from cmake to the build hooks:

To wire the deployment target into IOSCodeConfig (and optionally MacOSCodeConfig) following the same pattern as Android's targetAndroidNdkApi(environmentDefines), the version resolution should live in ios/native_assets.dart as a function of environmentDefines and be consumed by both IOSAssetTarget (during BuildHooks / LinkHooks) and copyNativeCodeAssetsIOS (during InstallCodeAssets).

Here is the step-by-step wiring across the 4 files:

1. Forward the Xcode build setting in xcode_backend.dart & build_info.dart

(PR #194146 already does this part for iOS)

  • In build_info.dart:
    const kIosDeploymentTarget = 'IosDeploymentTarget';
  • In xcode_backend.dart:
    final String? deploymentTarget = environment['IPHONEOS_DEPLOYMENT_TARGET'];
    if (deploymentTarget != null) {
      flutterArgs.add('-d$kIosDeploymentTarget=$deploymentTarget');
    }

2. Replace const targetIOSVersion = 15 with a Version-returning helper in ios/native_assets.dart

Mirror targetAndroidNdkApi(Map<String, String> environmentDefines) so both targets.dart and copyNativeCodeAssetsIOS use the same function (and use FlutterDarwinPlatform.ios.deploymentTarget() as the baseline):

Version targetIOSVersion(Map<String, String> environmentDefines) {
  final Version minimumSupported = FlutterDarwinPlatform.ios.deploymentTarget();
  final String? target = environmentDefines[kIosDeploymentTarget]?.trim();
  if (target == null || target.isEmpty) {
    return minimumSupported;
  }
  final Version? version = Version.parse(target);
  if (version == null || Version.versionPattern.firstMatch(target)?.end != target.length) {
    throwToolExit(
      'Invalid iOS deployment target "${environmentDefines[kIosDeploymentTarget]}". '
      'Expected a version such as 16, 16.4, or 17.2.1.',
    );
  }
  if (version < minimumSupported) {
    return minimumSupported;
  }
  // Ensure major-only inputs like "16" format as "16.0" in Info.plist while
  // preserving minor/patch versions like "16.4" or "17.2.1".
  if (!target.contains('.')) {
    return Version(version.major, version.minor, null);
  }
  return version;
}

3. Wire targetIOSVersion(environmentDefines) into IOSAssetTarget in targets.dart

IOSAssetTarget already receives environmentDefines, so _getIOSConfig in targets.dart only needs to call targetIOSVersion(environmentDefines).major:

  IOSCodeConfig _getIOSConfig(Map<String, String> environmentDefines, FileSystem fileSystem) {
    final String? sdkRoot = environmentDefines[kSdkRoot];
    if (sdkRoot == null) {
      throw MissingDefineException(kSdkRoot, 'native_assets');
    }
    final EnvironmentType environmentType = xcode.environmentTypeFromSdkroot(sdkRoot, fileSystem);
    return IOSCodeConfig(
      targetVersion: targetIOSVersion(environmentDefines).major,
      targetSdk: getIOSSdk(environmentType),
    );
  }

Because AssetBuildTarget.targetsFor(...) is called at the start of BuildHooks (and LinkHooks):

  • Build and link hooks receive input.config.code.iOS.targetVersion == 16 (instead of 15) when IPHONEOS_DEPLOYMENT_TARGET is 16.4.
  • Any malformed kIosDeploymentTarget fails fast during build_hooks before running any hooks or copying frameworks.
  • Once package:code_assets supports minor/patch versions in IOSCodeConfig, targetIOSVersion(environmentDefines) already returns a full Version object with .major, .minor, and .patch.

4. Use the same helper in installCodeAssets / copyNativeCodeAssetsIOS

Instead of passing raw String? iosDeploymentTarget and re-parsing it inside copyNativeCodeAssetsIOS, either:

  • Pass targetIOSVersion(environmentDefines) (or environmentDefines) to copyNativeCodeAssetsIOS, and pass minimumIOSVersion: deploymentTarget.toString() to createInfoPlist.

(And if doing macOS at the same time: MacOSAssetTarget can take Map<String, String> environmentDefines in _macTargets—and const <String, String>{} in FlutterTesterAssetTarget—and call targetMacOSVersion(environmentDefines).major.)

@leehack

leehack commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

@dcharkes Agreed that the app deployment target should reach both build/link hooks and the packaged metadata, and that iOS and macOS should be handled together.

The remaining precision prerequisite is in package:code_assets: both Apple targetVersion fields are integers, so passing .major still truncates values such as 16.4 or 17.2.1. I opened dart-lang/native#3741 and draft dart-lang/native#3742 proposing compatible optional minor/patch fields and migrating the C toolchain, Objective-C hook, and ffigen test hook. The draft covers iOS and macOS; it has 692 passing affected tests, but full CI is not green and the baseline failures are disclosed there.

Once the API shape is agreed and available for Flutter to consume, the follow-up here is to use one resolved version per platform for build/link configuration and metadata, forward MACOSX_DEPLOYMENT_TARGET as well as IPHONEOS_DEPLOYMENT_TARGET, and add propagation/cache/default/error tests for both platforms. I will preserve the existing native-assets floors (iOS 15 and macOS 13); the shared Darwin macOS floor is currently 12, so switching to it directly would change behavior.

This Flutter patch currently only fixes iOS framework metadata. It does not yet implement full hook propagation or the macOS configuration change.

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

Labels

team-ios Owned by iOS platform team team-macos Owned by the macOS platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants