diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index a42537c2bf4c33..3d6e955f8afd9b 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -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} diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index be6f94c2cc9c4f..d14cfa6efc3843 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -40,6 +40,7 @@ class GenSnapshot { @required SnapshotType snapshotType, @required String packagesPath, @required String depfilePath, + IOSArch iosArch, Iterable additionalArgs: const [], }) { final List args = [ @@ -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(['/usr/bin/arch', '-x86_64', snapshotterPath]..addAll(args)); + final String hostArch = iosArch == IOSArch.armv7 ? '-i386' : '-x86_64'; + return runCommandAndStreamOutput(['/usr/bin/arch', hostArch, snapshotterPath]..addAll(args)); } return runCommandAndStreamOutput([snapshotterPath]..addAll(args)); } @@ -137,12 +138,15 @@ class AOTSnapshotter { @required String outputPath, @required bool previewDart2, @required bool preferSharedLibrary, + IOSArch iosArch, List extraGenSnapshotOptions: const [], }) 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 _buildIosFramework({ + @required IOSArch iosArch, @required String assemblyPath, @required String outputPath, }) async { - printStatus('Building App.framework...'); - const List commonBuildOptions = const ['-arch', 'arm64', '-miphoneos-version-min=8.0']; + final String targetArch = iosArch == IOSArch.armv7 ? 'armv7' : 'arm64'; + printStatus('Building App.framework for $targetArch...'); + final List commonBuildOptions = ['-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(['-c', assemblyPath, '-o', assemblyO])); diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 82e7dead46f87b..2472b9df7a33dc 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -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 defaultIOSArchs = const [ + 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: diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index ee36b69d2181e6..d67c2a6def2ed1 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -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 buildArchs = argResults['ios-arch'].map(getIOSArchForName); + final Map iosBuilds = {}; + 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> exitCodes = >{}; + 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 dylibs = iosBuilds.values.map((String outputDir) => fs.path.join(outputDir, 'App.framework', 'App')); + fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync(); + await runCheckedAsync(['lipo'] + ..addAll(dylibs) + ..addAll(['-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. diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 08333f19e8fdc7..22489323b1a335 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -325,7 +325,6 @@ Future buildXcodeProject({ 'xcrun', 'xcodebuild', '-configuration', configuration, - 'ONLY_ACTIVE_ARCH=YES', ]; if (logger.isVerbose) { @@ -358,7 +357,7 @@ Future buildXcodeProject({ } if (buildForDevice) { - buildCommands.addAll(['-sdk', 'iphoneos', '-arch', 'arm64']); + buildCommands.addAll(['-sdk', 'iphoneos']); } else { buildCommands.addAll(['-sdk', 'iphonesimulator', '-arch', 'x86_64']); } diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart index a3a9a0eaed12f9..39244a21d31b35 100644 --- a/packages/flutter_tools/test/base/build_test.dart +++ b/packages/flutter_tools/test/base/build_test.dart @@ -52,6 +52,7 @@ class _FakeGenSnapshot implements GenSnapshot { SnapshotType snapshotType, String packagesPath, String depfilePath, + IOSArch iosArch, Iterable 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);