Repository navigation
[native assets] Respect app deployment targets on iOS and macOS #194145
Description
Use case
Native code assets may need a newer Apple deployment target than Flutter's baseline. The consuming app can already set IPHONEOS_DEPLOYMENT_TARGET or MACOSX_DEPLOYMENT_TARGET, but those values do not reach the corresponding native asset hook configuration.
Flutter currently supplies iOS 15 and macOS 13 through IOSCodeConfig and MacOSCodeConfig. In code_assets 2.1.0 both target-version fields are integers, so targets such as iOS 16.4 or macOS 13.3 cannot be represented accurately.
There is also a reproduced iOS packaging mismatch. With an app targeting 16.4 and a hook-provided binary requiring 16.4, Flutter generates a native framework plist declaring 15.0:
| Artifact | Minimum OS version |
|---|---|
| Runner.app/Info.plist | 16.4 |
| Native binary LC_BUILD_VERSION / minos | 16.4 |
| Generated native framework Info.plist | 15.0 |
This was reproduced in successful unsigned release builds on stable 3.47.6 (5fc346839b) and master 3.49.0-0.2.pre (a381eea6b1) using a small C function, without a third-party native runtime or model download. The same reduced sample's device and simulator framework plists become 16.4 with an iOS packaging patch. This evidence does not establish an App Store rejection or a runtime failure.
A workaround in llamadart is a separate Flutter companion package linking an XCFramework through SwiftPM. Hook packages cannot set the generated framework's plist themselves. The hook in this use case downloads prebuilt libraries, so fixing iOS packaging can help independently of changing the compiler hook input.
macOS's fixed hook configuration is confirmed in source, but an equivalent packaging or runtime failure has not been reproduced. macOS has a different framework layout, and its app uses LSMinimumSystemVersion rather than the iOS MinimumOSVersion path.
Proposal
- Pass the consuming app's iOS/macOS deployment target into native asset configuration, preserving major, minor, and patch versions. Full hook-version support will need coordination with
dart-lang/native. - Keep iOS framework metadata consistent with the app deployment target rather than a fixed baseline. Preserve Flutter's supported floor and a documented fallback when no app target is available.
- Take settings from the app build context; dependency packages should not need their own
ios/ormacos/directories. - Invalidate affected cached output when the deployment target changes, and test defaults, malformed/unsupported values, iOS device/simulator builds, and supported macOS architectures. macOS needs its own reproduction and validation before extending the packaging change.
Partial implementation: #194146 fixes iOS framework metadata without changing the hooks protocol. It should be a partial fix, leaving this issue open for hook input and macOS work. This proposal does not claim that deployment-version changes alone replace every function of companion packages, including privacy manifest packaging.
Related: #145104 consolidates minimum-version constants; this proposal concerns propagation of a higher app-specific target. dart-lang/native#3594 discusses the Flutter 3.47 baseline, and #191964 raised the fixed iOS value to 15.
Verified reduced iOS reproducer
Create an app with flutter create --platforms=ios native_asset_deployment_repro. Set IPHONEOS_DEPLOYMENT_TARGET = 16.4 for all Runner configurations, add the files below, and add this dependency to the app's pubspec.yaml:
ios_native_asset_probe:
path: probeThe hook deliberately compiles at a fixed 16.4 minimum to model a prebuilt library with that requirement, rather than deriving it from the current integer hook input. It supports arm64 devices and arm64/x86_64 simulators.
probe/pubspec.yaml:
name: ios_native_asset_probe
description: A tiny native library with a fixed iOS 16.4 requirement.
publish_to: none
environment:
sdk: ^3.13.0
dependencies:
hooks: 2.2.0
code_assets: 2.1.0probe/src/probe.c:
int native_asset_probe(void) { return 42; }probe/lib/probe.dart:
import 'dart:ffi';
@Native<Int32 Function()>(symbol: 'native_asset_probe')
external int nativeAssetProbe();probe/hook/build.dart:
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
if (!input.config.buildCodeAssets) return;
final code = input.config.code;
if (code.targetOS != OS.iOS) {
throw UnsupportedError('This reduced sample supports iOS only.');
}
final simulator = code.iOS.targetSdk == IOSSdk.iPhoneSimulator;
final architecture = switch (code.targetArchitecture) {
Architecture.arm64 => 'arm64',
Architecture.x64 when simulator => 'x86_64',
_ => throw UnsupportedError('Unsupported iOS architecture.'),
};
final sdk = simulator ? 'iphonesimulator' : 'iphoneos';
final sdkResult = await Process.run('xcrun', [
'--sdk',
sdk,
'--show-sdk-path',
]);
if (sdkResult.exitCode != 0) throw StateError('${sdkResult.stderr}');
final library = input.outputDirectory.resolve('libdeployment_probe.dylib');
final source = input.packageRoot.resolve('src/probe.c');
await Directory.fromUri(input.outputDirectory).create(recursive: true);
final result = await Process.run('xcrun', [
'--sdk',
sdk,
'clang',
'-dynamiclib',
'-target',
'$architecture-apple-ios16.4${simulator ? '-simulator' : ''}',
'-isysroot',
(sdkResult.stdout as String).trim(),
'-Wl,-install_name,@rpath/libdeployment_probe.dylib',
source.toFilePath(),
'-o',
library.toFilePath(),
]);
if (result.exitCode != 0) throw StateError('${result.stderr}');
output.dependencies.add(source);
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: 'probe.dart',
linkMode: DynamicLoadingBundled(),
file: library,
),
);
});
}lib/main.dart:
import 'package:flutter/material.dart';
import 'package:ios_native_asset_probe/probe.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(child: Text('Native result: ${nativeAssetProbe()}')),
),
),
);
}Run:
flutter pub get
flutter build ios --release --no-codesign
/usr/libexec/PlistBuddy -c 'Print :MinimumOSVersion' build/ios/iphoneos/Runner.app/Info.plist
/usr/libexec/PlistBuddy -c 'Print :MinimumOSVersion' build/ios/iphoneos/Runner.app/Frameworks/deployment_probe.framework/Info.plist
xcrun vtool -show-build build/ios/iphoneos/Runner.app/Frameworks/deployment_probe.framework/deployment_probeUnpatched result: app 16.4, binary minos 16.4, native framework plist 15.0. Requires Xcode with the iOS SDK; tested on an Apple Silicon Mac with Xcode 27.0 (27A266a), macOS 26.7.1. Builds require no connected device. No simulator or device execution was performed.
Activity
dcharkes commentedon Oct 11, 2026
Yes, this is a known issue. The min os target is hardcoded in various places in the codebase with TODOs to have it from one place. And that one place should come from the native build system invoking
flutter assemble. (We had some similar fixes that wired through the c-compiler from CMake in Windows/Linux builds to the hooks inputs.)Happy to receive PRs to fix this! 🙏
cc @bkonyi
leehack commentedon Oct 11, 2026
@dcharkes here is the PR :)
puneetkukreja98 commentedon Oct 11, 2026
@leehack Thanks for reporting this, routing this to team-ios for further investigation.