diff --git a/dev/devicelab/bin/tasks/build_ios_framework_module_test.dart b/dev/devicelab/bin/tasks/build_ios_framework_module_test.dart index 2b11560c6..d23ce3ce8 100644 --- a/dev/devicelab/bin/tasks/build_ios_framework_module_test.dart +++ b/dev/devicelab/bin/tasks/build_ios_framework_module_test.dart @@ -79,18 +79,33 @@ Future main() async { 'App', )); - final String aotSymbols = await dylibSymbols(path.join( + final String appFrameworkPath = path.join( outputPath, 'Debug', 'App.framework', 'App', - )); + ); + final String aotSymbols = await dylibSymbols(appFrameworkPath); if (aotSymbols.contains('architecture') || aotSymbols.contains('_kDartVmSnapshot')) { throw TaskResult.failure('Debug App.framework contains AOT'); } + final String debugAppArchs = await fileType(appFrameworkPath); + + if (!debugAppArchs.contains('armv7')) { + throw TaskResult.failure('Debug App.framework armv7 architecture missing'); + } + + if (!debugAppArchs.contains('arm64')) { + throw TaskResult.failure('Debug App.framework arm64 architecture missing'); + } + + if (!debugAppArchs.contains('x86_64')) { + throw TaskResult.failure('Debug App.framework x86_64 architecture missing'); + } + section('Check profile, release builds has Dart AOT dylib'); for (String mode in ['Profile', 'Release']) { @@ -116,6 +131,10 @@ Future main() async { throw TaskResult.failure('$mode App.framework arm64 architecture missing'); } + if (aotSymbols.contains('x86_64')) { + throw TaskResult.failure('$mode App.framework contains x86_64 architecture'); + } + if (!aotSymbols.contains('_kDartVmSnapshot')) { throw TaskResult.failure('$mode App.framework missing Dart AOT'); } diff --git a/dev/devicelab/lib/framework/ios.dart b/dev/devicelab/lib/framework/ios.dart index 01ffd0c0f..c3380fe0b 100644 --- a/dev/devicelab/lib/framework/ios.dart +++ b/dev/devicelab/lib/framework/ios.dart @@ -53,3 +53,7 @@ Future> measureIosCpuGpu({ Future dylibSymbols(String pathToDylib) { return eval('nm', ['-g', pathToDylib]); } + +Future fileType(String pathToDylib) { + return eval('file', [pathToDylib]); +} diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index cc1be5485..32aa1890e 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -244,7 +244,7 @@ class AOTSnapshotter { final String assemblyO = fs.path.join(outputPath, 'snapshot_assembly.o'); List isysrootArgs; if (isIOS) { - final String iPhoneSDKLocation = await xcode.iPhoneSdkLocation(); + final String iPhoneSDKLocation = await xcode.sdkLocation(SdkType.iPhone); if (iPhoneSDKLocation != null) { isysrootArgs = ['-isysroot', iPhoneSDKLocation]; } diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 3b24bb3f8..47b41f4aa 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -157,17 +157,11 @@ class AotAssemblyProfile extends AotAssemblyBase { /// This framework needs to exist for the Xcode project to link/bundle, /// but it isn't actually executed. To generate something valid, we compile a trivial /// constant. -Future createStubAppFramework(Directory appFrameworkDirectory) async { - File outputFile; +Future createStubAppFramework(File outputFile, SdkType sdk) async { try { - if (!appFrameworkDirectory.existsSync()) { - appFrameworkDirectory.createSync(recursive: true); - } - - outputFile = appFrameworkDirectory.childFile('App'); outputFile.createSync(recursive: true); } catch (e) { - throwToolExit('Failed to create App.framework stub at ${appFrameworkDirectory.path}'); + throwToolExit('Failed to create App.framework stub at ${outputFile.path}'); } final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_stub_source.'); @@ -177,14 +171,32 @@ Future createStubAppFramework(Directory appFrameworkDirectory) async static const int Moo = 88; '''); + List archFlags; + if (sdk == SdkType.iPhone) { + archFlags = [ + '-arch', + getNameForDarwinArch(DarwinArch.armv7), + '-arch', + getNameForDarwinArch(DarwinArch.arm64), + ]; + } else { + archFlags = [ + '-arch', + getNameForDarwinArch(DarwinArch.x86_64), + ]; + } + return await xcode.clang([ '-x', 'c', + ...archFlags, stubSource.path, '-dynamiclib', + '-fembed-bitcode-marker', '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', '-install_name', '@rpath/App.framework/App', + '-isysroot', await xcode.sdkLocation(sdk), '-o', outputFile.path, ]); } finally { @@ -192,6 +204,8 @@ Future createStubAppFramework(Directory appFrameworkDirectory) async tempDir.deleteSync(recursive: true); } on FileSystemException catch (_) { // Best effort. Sometimes we can't delete things from system temp. + } catch (e) { + throwToolExit('Failed to create App.framework stub at ${outputFile.path}'); } } } diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index d8dfb32c3..96a5c7d0a 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -166,7 +166,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { await _produceFlutterFramework(outputDirectory, mode, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory); // Build aot, create module.framework and copy. - await _produceAppFramework(mode, iPhoneBuildOutput, modeDirectory); + await _produceAppFramework(mode, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory); // Build and copy plugins. await processPodsIfNeeded(_project.ios, getIosBuildDirectory(), mode); @@ -254,13 +254,14 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { status.stop(); } - Future _produceAppFramework(BuildMode mode, Directory iPhoneBuildOutput, Directory modeDirectory) async { + Future _produceAppFramework(BuildMode mode, Directory iPhoneBuildOutput, Directory simulatorBuildOutput, Directory modeDirectory) async { const String appFrameworkName = 'App.framework'; final Directory destinationAppFrameworkDirectory = modeDirectory.childDirectory(appFrameworkName); + destinationAppFrameworkDirectory.createSync(recursive: true); if (mode == BuildMode.debug) { final Status status = logger.startProgress(' ├─Add placeholder App.framework for debug...', timeout: timeoutConfiguration.fastOperation); - await createStubAppFramework(destinationAppFrameworkDirectory); + await _produceStubAppFrameworkIfNeeded(mode, iPhoneBuildOutput, simulatorBuildOutput, destinationAppFrameworkDirectory); status.stop(); } else { await _produceAotAppFrameworkIfNeeded(mode, iPhoneBuildOutput, destinationAppFrameworkDirectory); @@ -283,6 +284,37 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { status.stop(); } + Future _produceStubAppFrameworkIfNeeded(BuildMode mode, Directory iPhoneBuildOutput, Directory simulatorBuildOutput, Directory destinationAppFrameworkDirectory) async { + if (mode != BuildMode.debug) { + return; + } + const String appFrameworkName = 'App.framework'; + const String binaryName = 'App'; + + final Directory iPhoneAppFrameworkDirectory = iPhoneBuildOutput.childDirectory(appFrameworkName); + final File iPhoneAppFrameworkFile = iPhoneAppFrameworkDirectory.childFile(binaryName); + await createStubAppFramework(iPhoneAppFrameworkFile, SdkType.iPhone); + + final Directory simulatorAppFrameworkDirectory = simulatorBuildOutput.childDirectory(appFrameworkName); + final File simulatorAppFrameworkFile = simulatorAppFrameworkDirectory.childFile(binaryName); + await createStubAppFramework(simulatorAppFrameworkFile, SdkType.iPhoneSimulator); + + final List lipoCommand = [ + 'xcrun', + 'lipo', + '-create', + iPhoneAppFrameworkFile.path, + simulatorAppFrameworkFile.path, + '-output', + destinationAppFrameworkDirectory.childFile(binaryName).path + ]; + + await processUtils.run( + lipoCommand, + allowReentrantFlutter: false, + ); + } + Future _produceAotAppFrameworkIfNeeded(BuildMode mode, Directory iPhoneBuildOutput, Directory destinationAppFrameworkDirectory) async { if (mode == BuildMode.debug) { return; @@ -295,6 +327,7 @@ class BuildIOSFrameworkCommand extends BuildSubCommand { // Relative paths show noise in the compiler https://github.com/dart-lang/sdk/issues/37978. mainDartFile: fs.path.absolute(targetFile), quiet: true, + bitcode: true, reportTimings: false, iosBuildArchs: [DarwinArch.armv7, DarwinArch.arm64], dartDefines: dartDefines, diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart index 28a733d22..6c40ce880 100644 --- a/packages/flutter_tools/lib/src/macos/xcode.dart +++ b/packages/flutter_tools/lib/src/macos/xcode.dart @@ -17,6 +17,31 @@ const int kXcodeRequiredVersionMinor = 2; Xcode get xcode => context.get(); +enum SdkType { + iPhone, + iPhoneSimulator, + macOS, +} + +/// SDK name passed to `xcrun --sdk`. Corresponds to undocumented Xcode +/// SUPPORTED_PLATFORMS values. +/// +/// Usage: xcrun [options] ... arguments ... +/// ... +/// --sdk find the tool for the given SDK name +String getNameForSdk(SdkType sdk) { + switch (sdk) { + case SdkType.iPhone: + return 'iphoneos'; + case SdkType.iPhoneSimulator: + return 'iphonesimulator'; + case SdkType.macOS: + return 'macosx'; + } + assert(false); + return null; +} + class Xcode { bool get isInstalledAndMeetsVersionCheck => platform.isMacOS && isInstalled && isVersionSatisfactory; @@ -117,9 +142,10 @@ class Xcode { ); } - Future iPhoneSdkLocation() async { + Future sdkLocation(SdkType sdk) async { + assert(sdk != null); final RunResult runResult = await processUtils.run( - ['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'], + ['xcrun', '--sdk', getNameForSdk(sdk), '--show-sdk-path'], throwOnError: true, ); if (runResult.exitCode != 0) { diff --git a/packages/flutter_tools/test/general.shard/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart index 07ef6a0bc..ecea2c3ad 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -244,7 +244,7 @@ void main() { mockAndroidSdk = MockAndroidSdk(); mockArtifacts = MockArtifacts(); mockXcode = MockXcode(); - when(mockXcode.iPhoneSdkLocation()).thenAnswer((_) => Future.value(kSDKPath)); + when(mockXcode.sdkLocation(any)).thenAnswer((_) => Future.value(kSDKPath)); bufferLogger = BufferLogger(); for (BuildMode mode in BuildMode.values) { diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index 8e20841f5..8a5672154 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -190,5 +190,11 @@ void main() { }, overrides: { ProcessManager: () => mockProcessManager, }); + + testUsingContext('SDK name', () { + expect(getNameForSdk(SdkType.iPhone), 'iphoneos'); + expect(getNameForSdk(SdkType.iPhoneSimulator), 'iphonesimulator'); + expect(getNameForSdk(SdkType.macOS), 'macosx'); + }); }); }