Unverified Commit 849676fc authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Support multi-arch iOS binaries (#17312)

This change adds support for armv7, arm64, and universal iOS apps.

This change eliminates iOS target architecture hardcoding (previously
arm64 only) and uses the target architecture(s) specified in Xcode's
ARCHS setting ('Architectures' in Xcode Build Settings).

For universal binaries, set ARCHS to its default value, $(ARCHS_STANDARD).

Note that after changing the architecture in Xcode, developers should
run 'pod install' from the ios subdirectory of their project. A separate
change (that will land before this one) will add support for
automatically detecting project file and Podfile changes and re-running
pod install if necessary.

This change also adds an --ios-arch option to flutter build aot. In iOS
AOT builds (in profile and release mode), this dictates which
architectures are built into App.framework. This flag should generally
be unnecessary to set manually since flutter build aot is typically only
invoked internally by flutter itself.
parent 5834f417
......@@ -111,6 +111,8 @@ BuildApp() {
if [[ "${build_mode}" != "debug" ]]; then
StreamOutput " ├─Building Dart code..."
# Transform ARCHS to comma-separated list of target architectures.
local archs="${ARCHS// /,}"
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \
build aot \
......@@ -118,6 +120,7 @@ BuildApp() {
--target-platform=ios \
--target="${target_path}" \
--${build_mode} \
--ios-arch="${archs}" \
${local_engine_flag} \
${preview_dart_2_flag}
......
......@@ -40,6 +40,7 @@ class GenSnapshot {
@required SnapshotType snapshotType,
@required String packagesPath,
@required String depfilePath,
IOSArch iosArch,
Iterable<String> additionalArgs: const <String>[],
}) {
final List<String> args = <String>[
......@@ -56,8 +57,8 @@ class GenSnapshot {
// code. /usr/bin/arch can be used to run binaries with the specified
// architecture.
if (snapshotType.platform == TargetPlatform.ios) {
// TODO(cbracken): for the moment, always generate only arm64 code.
return runCommandAndStreamOutput(<String>['/usr/bin/arch', '-x86_64', snapshotterPath]..addAll(args));
final String hostArch = iosArch == IOSArch.armv7 ? '-i386' : '-x86_64';
return runCommandAndStreamOutput(<String>['/usr/bin/arch', hostArch, snapshotterPath]..addAll(args));
}
return runCommandAndStreamOutput(<String>[snapshotterPath]..addAll(args));
}
......@@ -137,12 +138,15 @@ class AOTSnapshotter {
@required String outputPath,
@required bool previewDart2,
@required bool preferSharedLibrary,
IOSArch iosArch,
List<String> extraGenSnapshotOptions: const <String>[],
}) async {
if (!_isValidAotPlatform(platform, buildMode)) {
printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
return -1;
}
// TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}.
assert(platform != TargetPlatform.ios || iosArch != null);
final bool compileToSharedLibrary = preferSharedLibrary && androidSdk.ndkCompiler != null;
if (preferSharedLibrary && !compileToSharedLibrary) {
......@@ -215,7 +219,7 @@ class AOTSnapshotter {
]);
}
if (platform == TargetPlatform.android_arm) {
if (platform == TargetPlatform.android_arm || iosArch == IOSArch.armv7) {
// Not supported by the Pixel in 32-bit mode.
genSnapshotArgs.add('--no-use-integer-division');
}
......@@ -253,6 +257,7 @@ class AOTSnapshotter {
packagesPath: packageMap.packagesPath,
depfilePath: depfilePath,
additionalArgs: genSnapshotArgs,
iosArch: iosArch,
);
if (genSnapshotExitCode != 0) {
printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
......@@ -266,7 +271,7 @@ class AOTSnapshotter {
// On iOS, we use Xcode to compile the snapshot into a dynamic library that the
// end-developer can link into their app.
if (platform == TargetPlatform.ios) {
final RunResult result = await _buildIosFramework(assemblyPath: assembly, outputPath: outputDir.path);
final RunResult result = await _buildIosFramework(iosArch: iosArch, assemblyPath: assembly, outputPath: outputDir.path);
if (result.exitCode != 0)
return result.exitCode;
} else if (compileToSharedLibrary) {
......@@ -283,11 +288,13 @@ class AOTSnapshotter {
/// Builds an iOS framework at [outputPath]/App.framework from the assembly
/// source at [assemblyPath].
Future<RunResult> _buildIosFramework({
@required IOSArch iosArch,
@required String assemblyPath,
@required String outputPath,
}) async {
printStatus('Building App.framework...');
const List<String> commonBuildOptions = const <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
final String targetArch = iosArch == IOSArch.armv7 ? 'armv7' : 'arm64';
printStatus('Building App.framework for $targetArch...');
final List<String> commonBuildOptions = <String>['-arch', targetArch, '-miphoneos-version-min=8.0'];
final String assemblyO = fs.path.join(outputPath, 'snapshot_assembly.o');
final RunResult compileResult = await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', assemblyPath, '-o', assemblyO]));
......
......@@ -160,6 +160,41 @@ enum TargetPlatform {
tester,
}
/// iOS target device architecture.
//
// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64.
enum IOSArch {
armv7,
arm64,
}
/// The default set of iOS device architectures to build for.
const List<IOSArch> defaultIOSArchs = const <IOSArch>[
IOSArch.arm64,
];
String getNameForIOSArch(IOSArch arch) {
switch (arch) {
case IOSArch.armv7:
return 'armv7';
case IOSArch.arm64:
return 'arm64';
}
assert(false);
return null;
}
IOSArch getIOSArchForName(String arch) {
switch (arch) {
case 'armv7':
return IOSArch.armv7;
case 'arm64':
return IOSArch.arm64;
}
assert(false);
return null;
}
String getNameForTargetPlatform(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android_arm:
......
......@@ -8,6 +8,7 @@ import '../base/build.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../dart/package_map.dart';
import '../globals.dart';
......@@ -32,6 +33,12 @@ class BuildAotCommand extends BuildSubCommand {
hide: !verboseHelp,
help: 'Preview Dart 2.0 functionality.',
)
..addMultiOption('ios-arch',
splitCommas: true,
defaultsTo: defaultIOSArchs.map(getNameForIOSArch),
allowed: IOSArch.values.map(getNameForIOSArch),
help: 'iOS architectures to build',
)
..addMultiOption(FlutterOptions.kExtraFrontEndOptions,
splitCommas: true,
hide: true,
......@@ -90,18 +97,57 @@ class BuildAotCommand extends BuildSubCommand {
}
// Build AOT snapshot.
final int snapshotExitCode = await snapshotter.build(
platform: platform,
buildMode: buildMode,
mainPath: mainPath,
packagesPath: PackageMap.globalPackagesPath,
outputPath: outputPath,
previewDart2: previewDart2,
preferSharedLibrary: argResults['prefer-shared-library'],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
);
if (snapshotExitCode != 0) {
printError('Snapshotting exited with non-zero exit code: $snapshotExitCode');
if (platform == TargetPlatform.ios) {
// Determine which iOS architectures to build for.
final Iterable<IOSArch> buildArchs = argResults['ios-arch'].map(getIOSArchForName);
final Map<IOSArch, String> iosBuilds = <IOSArch, String>{};
for (IOSArch arch in buildArchs)
iosBuilds[arch] = fs.path.join(outputPath, getNameForIOSArch(arch));
// Generate AOT snapshot and compile to arch-specific App.framework.
final Map<IOSArch, Future<int>> exitCodes = <IOSArch, Future<int>>{};
iosBuilds.forEach((IOSArch iosArch, String outputPath) {
exitCodes[iosArch] = snapshotter.build(
platform: platform,
iosArch: iosArch,
buildMode: buildMode,
mainPath: mainPath,
packagesPath: PackageMap.globalPackagesPath,
outputPath: outputPath,
previewDart2: previewDart2,
preferSharedLibrary: false,
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
).then((int buildExitCode) {
if (buildExitCode != 0)
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
return buildExitCode;
});
});
// Merge arch-specific App.frameworks into a multi-arch App.framework.
if ((await Future.wait(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) {
final Iterable<String> dylibs = iosBuilds.values.map((String outputDir) => fs.path.join(outputDir, 'App.framework', 'App'));
fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync();
await runCheckedAsync(<String>['lipo']
..addAll(dylibs)
..addAll(<String>['-create', '-output', fs.path.join(outputPath, 'App.framework', 'App')]),
);
}
} else {
// Android AOT snapshot.
final int snapshotExitCode = await snapshotter.build(
platform: platform,
buildMode: buildMode,
mainPath: mainPath,
packagesPath: PackageMap.globalPackagesPath,
outputPath: outputPath,
previewDart2: previewDart2,
preferSharedLibrary: argResults['prefer-shared-library'],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
);
if (snapshotExitCode != 0) {
printError('Snapshotting exited with non-zero exit code: $snapshotExitCode');
}
}
} on String catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below.
......
......@@ -325,7 +325,6 @@ Future<XcodeBuildResult> buildXcodeProject({
'xcrun',
'xcodebuild',
'-configuration', configuration,
'ONLY_ACTIVE_ARCH=YES',
];
if (logger.isVerbose) {
......@@ -358,7 +357,7 @@ Future<XcodeBuildResult> buildXcodeProject({
}
if (buildForDevice) {
buildCommands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
buildCommands.addAll(<String>['-sdk', 'iphoneos']);
} else {
buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
}
......
......@@ -52,6 +52,7 @@ class _FakeGenSnapshot implements GenSnapshot {
SnapshotType snapshotType,
String packagesPath,
String depfilePath,
IOSArch iosArch,
Iterable<String> additionalArgs,
}) async {
_callCount += 1;
......@@ -361,6 +362,7 @@ void main() {
outputPath: outputPath,
preferSharedLibrary: false,
previewDart2: true,
iosArch: IOSArch.arm64,
);
expect(genSnapshotExitCode, 0);
......@@ -407,6 +409,7 @@ void main() {
outputPath: outputPath,
preferSharedLibrary: false,
previewDart2: true,
iosArch: IOSArch.arm64,
);
expect(genSnapshotExitCode, 0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment