"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter_tools/lib/src/commands/build_ios_framework.dart" (13 Nov 2020, 26594 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "build_ios_framework.dart": 1.22.3_vs_1.22.4.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 import 'dart:async';
    6 
    7 import 'package:file/file.dart';
    8 import 'package:meta/meta.dart';
    9 
   10 import '../artifacts.dart';
   11 import '../base/common.dart';
   12 import '../base/file_system.dart';
   13 import '../base/logger.dart';
   14 import '../base/platform.dart';
   15 import '../base/process.dart';
   16 import '../base/utils.dart';
   17 import '../build_info.dart';
   18 import '../build_system/build_system.dart';
   19 import '../build_system/targets/common.dart';
   20 import '../build_system/targets/icon_tree_shaker.dart';
   21 import '../build_system/targets/ios.dart';
   22 import '../cache.dart';
   23 import '../convert.dart';
   24 import '../globals.dart' as globals;
   25 import '../macos/cocoapod_utils.dart';
   26 import '../plugins.dart';
   27 import '../project.dart';
   28 import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
   29 import '../version.dart';
   30 import 'build.dart';
   31 
   32 /// Produces a .framework for integration into a host iOS app. The .framework
   33 /// contains the Flutter engine and framework code as well as plugins. It can
   34 /// be integrated into plain Xcode projects without using or other package
   35 /// managers.
   36 class BuildIOSFrameworkCommand extends BuildSubCommand {
   37   BuildIOSFrameworkCommand({
   38     FlutterVersion flutterVersion, // Instantiating FlutterVersion kicks off networking, so delay until it's needed, but allow test injection.
   39     @required BuildSystem buildSystem,
   40     @required bool verboseHelp,
   41     Cache cache,
   42     Platform platform
   43   }) : _flutterVersion = flutterVersion,
   44        _buildSystem = buildSystem,
   45        _injectedCache = cache,
   46        _injectedPlatform = platform {
   47     addTreeShakeIconsFlag();
   48     usesTargetOption();
   49     usesFlavorOption();
   50     usesPubOption();
   51     usesDartDefineOption();
   52     addSplitDebugInfoOption();
   53     addDartObfuscationOption();
   54     usesExtraFrontendOptions();
   55     addNullSafetyModeOptions(hide: !verboseHelp);
   56     addEnableExperimentation(hide: !verboseHelp);
   57 
   58     argParser
   59       ..addFlag('debug',
   60         negatable: true,
   61         defaultsTo: true,
   62         help: 'Whether to produce a framework for the debug build configuration. '
   63               'By default, all build configurations are built.'
   64       )
   65       ..addFlag('profile',
   66         negatable: true,
   67         defaultsTo: true,
   68         help: 'Whether to produce a framework for the profile build configuration. '
   69               'By default, all build configurations are built.'
   70       )
   71       ..addFlag('release',
   72         negatable: true,
   73         defaultsTo: true,
   74         help: 'Whether to produce a framework for the release build configuration. '
   75               'By default, all build configurations are built.'
   76       )
   77       ..addFlag('universal',
   78         help: 'Produce universal frameworks that include all valid architectures. '
   79               'This is true by default.',
   80         defaultsTo: true,
   81         negatable: true
   82       )
   83       ..addFlag('xcframework',
   84         help: 'Produce xcframeworks that include all valid architectures (Xcode 11 or later).',
   85       )
   86       ..addFlag('cocoapods',
   87         help: 'Produce a Flutter.podspec instead of an engine Flutter.framework (recomended if host app uses CocoaPods).',
   88       )
   89       ..addOption('output',
   90         abbr: 'o',
   91         valueHelp: 'path/to/directory/',
   92         help: 'Location to write the frameworks.',
   93       )
   94       ..addFlag('force',
   95         abbr: 'f',
   96         help: 'Force Flutter.podspec creation on the master channel. For testing only.',
   97         hide: true
   98       );
   99   }
  100 
  101   final BuildSystem _buildSystem;
  102   BuildSystem get buildSystem => _buildSystem ?? globals.buildSystem;
  103 
  104   Cache get _cache => _injectedCache ?? globals.cache;
  105   final Cache _injectedCache;
  106 
  107   Platform get _platform => _injectedPlatform ?? globals.platform;
  108   final Platform _injectedPlatform;
  109 
  110   FlutterVersion _flutterVersion;
  111 
  112   @override
  113   final String name = 'ios-framework';
  114 
  115   @override
  116   final String description = 'Produces a .framework directory for a Flutter module '
  117       'and its plugins for integration into existing, plain Xcode projects.\n'
  118       'This can only be run on macOS hosts.';
  119 
  120   @override
  121   Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
  122     DevelopmentArtifact.iOS,
  123   };
  124 
  125   FlutterProject _project;
  126 
  127   List<BuildInfo> get buildInfos {
  128     final List<BuildInfo> buildInfos = <BuildInfo>[];
  129 
  130     if (boolArg('debug')) {
  131       buildInfos.add(getBuildInfo(forcedBuildMode: BuildMode.debug));
  132     }
  133     if (boolArg('profile')) {
  134       buildInfos.add(getBuildInfo(forcedBuildMode: BuildMode.profile));
  135     }
  136     if (boolArg('release')) {
  137       buildInfos.add(getBuildInfo(forcedBuildMode: BuildMode.release));
  138     }
  139 
  140     return buildInfos;
  141   }
  142 
  143   @override
  144   Future<void> validateCommand() async {
  145     await super.validateCommand();
  146     _project = FlutterProject.current();
  147     if (!_project.isModule) {
  148       throwToolExit('Building frameworks for iOS is only supported from a module.');
  149     }
  150 
  151     if (!_platform.isMacOS) {
  152       throwToolExit('Building frameworks for iOS is only supported on the Mac.');
  153     }
  154 
  155     if (!boolArg('universal') && !boolArg('xcframework')) {
  156       throwToolExit('--universal or --xcframework is required.');
  157     }
  158     if (boolArg('xcframework') && globals.xcode.majorVersion < 11) {
  159       throwToolExit('--xcframework requires Xcode 11.');
  160     }
  161     if (buildInfos.isEmpty) {
  162       throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.');
  163     }
  164   }
  165 
  166   @override
  167   Future<FlutterCommandResult> runCommand() async {
  168     final String outputArgument = stringArg('output')
  169         ?? globals.fs.path.join(globals.fs.currentDirectory.path, 'build', 'ios', 'framework');
  170 
  171     if (outputArgument.isEmpty) {
  172       throwToolExit('--output is required.');
  173     }
  174 
  175     if (!_project.ios.existsSync()) {
  176       throwToolExit('Module does not support iOS');
  177     }
  178 
  179     final Directory outputDirectory = globals.fs.directory(globals.fs.path.absolute(globals.fs.path.normalize(outputArgument)));
  180 
  181     for (final BuildInfo buildInfo in buildInfos) {
  182       final String productBundleIdentifier = await _project.ios.productBundleIdentifier(buildInfo);
  183       globals.printStatus('Building frameworks for $productBundleIdentifier in ${getNameForBuildMode(buildInfo.mode)} mode...');
  184       final String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(buildInfo.mode));
  185       final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration);
  186 
  187       if (modeDirectory.existsSync()) {
  188         modeDirectory.deleteSync(recursive: true);
  189       }
  190 
  191       if (boolArg('cocoapods')) {
  192         // FlutterVersion.instance kicks off git processing which can sometimes fail, so don't try it until needed.
  193         _flutterVersion ??= globals.flutterVersion;
  194         produceFlutterPodspec(buildInfo.mode, modeDirectory, force: boolArg('force'));
  195       } else {
  196         // Copy Flutter.framework.
  197         await _produceFlutterFramework(buildInfo, modeDirectory);
  198       }
  199 
  200       // Build aot, create module.framework and copy.
  201       await _produceAppFramework(buildInfo, modeDirectory);
  202 
  203       // Build and copy plugins.
  204       await processPodsIfNeeded(_project.ios, getIosBuildDirectory(), buildInfo.mode);
  205       final Directory iPhoneBuildOutput = modeDirectory.childDirectory('iphoneos');
  206       final Directory simulatorBuildOutput = modeDirectory.childDirectory('iphonesimulator');
  207       if (hasPlugins(_project)) {
  208         await _producePlugins(buildInfo.mode, xcodeBuildConfiguration, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory, outputDirectory);
  209       }
  210 
  211       final Status status = globals.logger.startProgress(
  212         ' └─Moving to ${globals.fs.path.relative(modeDirectory.path)}', timeout: timeoutConfiguration.slowOperation);
  213       try {
  214         // Delete the intermediaries since they would have been copied into our
  215         // output frameworks.
  216         if (iPhoneBuildOutput.existsSync()) {
  217           iPhoneBuildOutput.deleteSync(recursive: true);
  218         }
  219         if (simulatorBuildOutput.existsSync()) {
  220           simulatorBuildOutput.deleteSync(recursive: true);
  221         }
  222       } finally {
  223         status.stop();
  224       }
  225     }
  226 
  227     globals.printStatus('Frameworks written to ${outputDirectory.path}.');
  228     return FlutterCommandResult.success();
  229   }
  230 
  231   /// Create podspec that will download and unzip remote engine assets so host apps can leverage CocoaPods
  232   /// vendored framework caching.
  233   @visibleForTesting
  234   void produceFlutterPodspec(BuildMode mode, Directory modeDirectory, { bool force = false }) {
  235     final Status status = globals.logger.startProgress(' ├─Creating Flutter.podspec...', timeout: timeoutConfiguration.fastOperation);
  236     try {
  237       final GitTagVersion gitTagVersion = _flutterVersion.gitTagVersion;
  238       if (!force && (gitTagVersion.x == null || gitTagVersion.y == null || gitTagVersion.z == null || gitTagVersion.commits != 0)) {
  239         throwToolExit(
  240             '--cocoapods is only supported on the dev, beta, or stable channels. Detected version is ${_flutterVersion.frameworkVersion}');
  241       }
  242 
  243       // Podspecs use semantic versioning, which don't support hotfixes.
  244       // Fake out a semantic version with major.minor.(patch * 100) + hotfix.
  245       // A real increasing version is required to prompt CocoaPods to fetch
  246       // new artifacts when the source URL changes.
  247       final int minorHotfixVersion = gitTagVersion.z * 100 + (gitTagVersion.hotfix ?? 0);
  248 
  249       final File license = _cache.getLicenseFile();
  250       if (!license.existsSync()) {
  251         throwToolExit('Could not find license at ${license.path}');
  252       }
  253       final String licenseSource = license.readAsStringSync();
  254       final String artifactsMode = mode == BuildMode.debug ? 'ios' : 'ios-${mode.name}';
  255 
  256       final String podspecContents = '''
  257 Pod::Spec.new do |s|
  258   s.name                  = 'Flutter'
  259   s.version               = '${gitTagVersion.x}.${gitTagVersion.y}.$minorHotfixVersion' # ${_flutterVersion.frameworkVersion}
  260   s.summary               = 'Flutter Engine Framework'
  261   s.description           = <<-DESC
  262 Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
  263 This pod vends the iOS Flutter engine framework. It is compatible with application frameworks created with this version of the engine and tools.
  264 The pod version matches Flutter version major.minor.(patch * 100) + hotfix.
  265 DESC
  266   s.homepage              = 'https://flutter.dev'
  267   s.license               = { :type => 'MIT', :text => <<-LICENSE
  268 $licenseSource
  269 LICENSE
  270   }
  271   s.author                = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
  272   s.source                = { :http => '${_cache.storageBaseUrl}/flutter_infra/flutter/${_cache.engineRevision}/$artifactsMode/artifacts.zip' }
  273   s.documentation_url     = 'https://flutter.dev/docs'
  274   s.platform              = :ios, '8.0'
  275   s.vendored_frameworks   = 'Flutter.framework'
  276   s.prepare_command       = <<-CMD
  277 unzip Flutter.framework -d Flutter.framework
  278 CMD
  279 end
  280 ''';
  281 
  282       final File podspec = modeDirectory.childFile('Flutter.podspec')..createSync(recursive: true);
  283       podspec.writeAsStringSync(podspecContents);
  284     } finally {
  285       status.stop();
  286     }
  287   }
  288 
  289   Future<void> _produceFlutterFramework(
  290     BuildInfo buildInfo,
  291     Directory modeDirectory,
  292   ) async {
  293     final Status status = globals.logger.startProgress(
  294       ' ├─Populating Flutter.framework...',
  295       timeout: timeoutConfiguration.slowOperation,
  296     );
  297     final String engineCacheFlutterFrameworkDirectory = globals.artifacts.getArtifactPath(
  298       Artifact.flutterFramework,
  299       platform: TargetPlatform.ios,
  300       mode: buildInfo.mode,
  301     );
  302     final String flutterFrameworkFileName = globals.fs.path.basename(
  303       engineCacheFlutterFrameworkDirectory,
  304     );
  305     final Directory fatFlutterFrameworkCopy = modeDirectory.childDirectory(
  306       flutterFrameworkFileName,
  307     );
  308 
  309     try {
  310       // Copy universal engine cache framework to mode directory.
  311       globals.fsUtils.copyDirectorySync(
  312         globals.fs.directory(engineCacheFlutterFrameworkDirectory),
  313         fatFlutterFrameworkCopy,
  314       );
  315 
  316       if (buildInfo.mode != BuildMode.debug) {
  317         final File fatFlutterFrameworkBinary = fatFlutterFrameworkCopy.childFile('Flutter');
  318 
  319         // Remove simulator architecture in profile and release mode.
  320         final List<String> lipoCommand = <String>[
  321           ...globals.xcode.xcrunCommand(),
  322           'lipo',
  323           fatFlutterFrameworkBinary.path,
  324           '-remove',
  325           'x86_64',
  326           '-output',
  327           fatFlutterFrameworkBinary.path
  328         ];
  329         final RunResult lipoResult = await processUtils.run(
  330           lipoCommand,
  331           allowReentrantFlutter: false,
  332         );
  333 
  334         if (lipoResult.exitCode != 0) {
  335           throwToolExit(
  336             'Unable to remove simulator architecture in ${buildInfo.mode}: ${lipoResult.stderr}',
  337           );
  338         }
  339       }
  340     } finally {
  341       status.stop();
  342     }
  343 
  344     await _produceXCFramework(buildInfo, fatFlutterFrameworkCopy);
  345   }
  346 
  347   Future<void> _produceAppFramework(BuildInfo buildInfo, Directory modeDirectory) async {
  348     const String appFrameworkName = 'App.framework';
  349 
  350     final Status status = globals.logger.startProgress(
  351       ' ├─Building App.framework...',
  352       timeout: timeoutConfiguration.slowOperation,
  353     );
  354     try {
  355       Target target;
  356       if (buildInfo.isDebug) {
  357         target = const DebugIosApplicationBundle();
  358       } else if (buildInfo.isProfile) {
  359         target = const ProfileIosApplicationBundle();
  360       } else {
  361         target = const ReleaseIosApplicationBundle();
  362       }
  363 
  364       final Environment environment = Environment(
  365         projectDir: globals.fs.currentDirectory,
  366         outputDir: modeDirectory,
  367         buildDir: _project.dartTool.childDirectory('flutter_build'),
  368         cacheDir: null,
  369         flutterRootDir: globals.fs.directory(Cache.flutterRoot),
  370         defines: <String, String>{
  371           kTargetFile: targetFile,
  372           kBuildMode: getNameForBuildMode(buildInfo.mode),
  373           kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
  374           kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
  375           kDartDefines: jsonEncode(buildInfo.dartDefines),
  376           kBitcodeFlag: 'true',
  377           if (buildInfo?.extraGenSnapshotOptions?.isNotEmpty ?? false)
  378             kExtraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions.join(','),
  379           if (buildInfo?.extraFrontEndOptions?.isNotEmpty ?? false)
  380             kExtraFrontEndOptions: buildInfo.extraFrontEndOptions.join(','),
  381           kIosArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64]
  382             .map(getNameForDarwinArch).join(' '),
  383         },
  384         artifacts: globals.artifacts,
  385         fileSystem: globals.fs,
  386         logger: globals.logger,
  387         processManager: globals.processManager,
  388         engineVersion: globals.artifacts.isLocalEngine
  389           ? null
  390           : globals.flutterVersion.engineRevision,
  391       );
  392       final BuildResult result = await buildSystem.build(target, environment);
  393       if (!result.success) {
  394         for (final ExceptionMeasurement measurement in result.exceptions.values) {
  395           globals.printError(measurement.exception.toString());
  396         }
  397         throwToolExit('The App.framework build failed.');
  398       }
  399     } finally {
  400       status.stop();
  401     }
  402 
  403     final Directory destinationAppFrameworkDirectory = modeDirectory.childDirectory(appFrameworkName);
  404     await _produceXCFramework(buildInfo, destinationAppFrameworkDirectory);
  405   }
  406 
  407   Future<void> _producePlugins(
  408     BuildMode mode,
  409     String xcodeBuildConfiguration,
  410     Directory iPhoneBuildOutput,
  411     Directory simulatorBuildOutput,
  412     Directory modeDirectory,
  413     Directory outputDirectory,
  414   ) async {
  415     final Status status = globals.logger.startProgress(
  416       ' ├─Building plugins...', timeout: timeoutConfiguration.slowOperation);
  417     try {
  418       // Regardless of the last "flutter build" build mode,
  419       // copy the corresponding engine.
  420       // A plugin framework built with bitcode must link against the bitcode version
  421       // of Flutter.framework (Release).
  422       _project.ios.copyEngineArtifactToProject(mode);
  423 
  424       final String bitcodeGenerationMode = mode == BuildMode.release ?
  425           'bitcode' : 'marker'; // In release, force bitcode embedding without archiving.
  426 
  427       List<String> pluginsBuildCommand = <String>[
  428         ...globals.xcode.xcrunCommand(),
  429         'xcodebuild',
  430         '-alltargets',
  431         '-sdk',
  432         'iphoneos',
  433         '-configuration',
  434         xcodeBuildConfiguration,
  435         '-destination generic/platform=iOS',
  436         'SYMROOT=${iPhoneBuildOutput.path}',
  437         'BITCODE_GENERATION_MODE=$bitcodeGenerationMode',
  438         'ONLY_ACTIVE_ARCH=NO', // No device targeted, so build all valid architectures.
  439         'BUILD_LIBRARY_FOR_DISTRIBUTION=YES',
  440       ];
  441 
  442       RunResult buildPluginsResult = await processUtils.run(
  443         pluginsBuildCommand,
  444         workingDirectory: _project.ios.hostAppRoot.childDirectory('Pods').path,
  445         allowReentrantFlutter: false,
  446       );
  447 
  448       if (buildPluginsResult.exitCode != 0) {
  449         throwToolExit('Unable to build plugin frameworks: ${buildPluginsResult.stderr}');
  450       }
  451 
  452       if (mode == BuildMode.debug) {
  453         pluginsBuildCommand = <String>[
  454           ...globals.xcode.xcrunCommand(),
  455           'xcodebuild',
  456           '-alltargets',
  457           '-sdk',
  458           'iphonesimulator',
  459           '-configuration',
  460           xcodeBuildConfiguration,
  461           '-destination generic/platform=iOS',
  462           'SYMROOT=${simulatorBuildOutput.path}',
  463           'ARCHS=x86_64',
  464           'ONLY_ACTIVE_ARCH=NO', // No device targeted, so build all valid architectures.
  465           'BUILD_LIBRARY_FOR_DISTRIBUTION=YES',
  466         ];
  467 
  468         buildPluginsResult = await processUtils.run(
  469           pluginsBuildCommand,
  470           workingDirectory: _project.ios.hostAppRoot
  471             .childDirectory('Pods')
  472             .path,
  473           allowReentrantFlutter: false,
  474         );
  475 
  476         if (buildPluginsResult.exitCode != 0) {
  477           throwToolExit(
  478             'Unable to build plugin frameworks for simulator: ${buildPluginsResult.stderr}',
  479           );
  480         }
  481       }
  482 
  483       final Directory iPhoneBuildConfiguration = iPhoneBuildOutput.childDirectory(
  484         '$xcodeBuildConfiguration-iphoneos',
  485       );
  486       final Directory simulatorBuildConfiguration = simulatorBuildOutput.childDirectory(
  487         '$xcodeBuildConfiguration-iphonesimulator',
  488       );
  489 
  490       final Iterable<Directory> products = iPhoneBuildConfiguration
  491         .listSync(followLinks: false)
  492         .whereType<Directory>();
  493       for (final Directory builtProduct in products) {
  494         for (final FileSystemEntity podProduct in builtProduct.listSync(followLinks: false)) {
  495           final String podFrameworkName = podProduct.basename;
  496           if (globals.fs.path.extension(podFrameworkName) != '.framework') {
  497             continue;
  498           }
  499           final String binaryName = globals.fs.path.basenameWithoutExtension(podFrameworkName);
  500           if (boolArg('universal')) {
  501             globals.fsUtils.copyDirectorySync(
  502               podProduct as Directory,
  503               modeDirectory.childDirectory(podFrameworkName),
  504             );
  505             final List<String> lipoCommand = <String>[
  506               ...globals.xcode.xcrunCommand(),
  507               'lipo',
  508               '-create',
  509               globals.fs.path.join(podProduct.path, binaryName),
  510               if (mode == BuildMode.debug)
  511                 simulatorBuildConfiguration
  512                   .childDirectory(binaryName)
  513                   .childDirectory(podFrameworkName)
  514                   .childFile(binaryName)
  515                   .path,
  516               '-output',
  517               modeDirectory.childDirectory(podFrameworkName).childFile(binaryName).path
  518             ];
  519 
  520             final RunResult pluginsLipoResult = await processUtils.run(
  521               lipoCommand,
  522               workingDirectory: outputDirectory.path,
  523               allowReentrantFlutter: false,
  524             );
  525 
  526             if (pluginsLipoResult.exitCode != 0) {
  527               throwToolExit(
  528                 'Unable to create universal $binaryName.framework: ${buildPluginsResult.stderr}',
  529               );
  530             }
  531           }
  532 
  533           if (boolArg('xcframework')) {
  534             final List<String> xcframeworkCommand = <String>[
  535               ...globals.xcode.xcrunCommand(),
  536               'xcodebuild',
  537               '-create-xcframework',
  538               '-framework',
  539               podProduct.path,
  540               if (mode == BuildMode.debug)
  541                 '-framework',
  542               if (mode == BuildMode.debug)
  543                 simulatorBuildConfiguration
  544                   .childDirectory(binaryName)
  545                   .childDirectory(podFrameworkName)
  546                   .path,
  547               '-output',
  548               modeDirectory.childFile('$binaryName.xcframework').path
  549             ];
  550 
  551             final RunResult xcframeworkResult = await processUtils.run(
  552               xcframeworkCommand,
  553               workingDirectory: outputDirectory.path,
  554               allowReentrantFlutter: false,
  555             );
  556 
  557             if (xcframeworkResult.exitCode != 0) {
  558               throwToolExit(
  559                 'Unable to create $binaryName.xcframework: ${xcframeworkResult.stderr}',
  560               );
  561             }
  562           }
  563         }
  564       }
  565     } finally {
  566       status.stop();
  567     }
  568   }
  569 
  570   Future<void> _produceXCFramework(BuildInfo buildInfo, Directory fatFramework) async {
  571     if (boolArg('xcframework')) {
  572       final String frameworkBinaryName = globals.fs.path.basenameWithoutExtension(
  573           fatFramework.basename);
  574 
  575       final Status status = globals.logger.startProgress(
  576         ' ├─Creating $frameworkBinaryName.xcframework...',
  577         timeout: timeoutConfiguration.slowOperation,
  578       );
  579       try {
  580         if (buildInfo.mode == BuildMode.debug) {
  581           await _produceDebugXCFramework(fatFramework, frameworkBinaryName);
  582         } else {
  583           await _produceNonDebugXCFramework(buildInfo, fatFramework, frameworkBinaryName);
  584         }
  585       } finally {
  586         status.stop();
  587       }
  588     }
  589 
  590     if (!boolArg('universal')) {
  591       fatFramework.deleteSync(recursive: true);
  592     }
  593   }
  594 
  595   Future<void> _produceDebugXCFramework(Directory fatFramework, String frameworkBinaryName) async {
  596     final String frameworkFileName = fatFramework.basename;
  597     final File fatFlutterFrameworkBinary = fatFramework.childFile(
  598       frameworkBinaryName,
  599     );
  600     final Directory temporaryOutput = globals.fs.systemTempDirectory.createTempSync(
  601       'flutter_tool_build_ios_framework.',
  602     );
  603     try {
  604       // Copy universal framework to variant directory.
  605       final Directory iPhoneBuildOutput = temporaryOutput.childDirectory(
  606         'ios',
  607       )..createSync(recursive: true);
  608       final Directory simulatorBuildOutput = temporaryOutput.childDirectory(
  609         'simulator',
  610       )..createSync(recursive: true);
  611       final Directory armFlutterFrameworkDirectory = iPhoneBuildOutput
  612         .childDirectory(frameworkFileName);
  613       final File armFlutterFrameworkBinary = armFlutterFrameworkDirectory
  614         .childFile(frameworkBinaryName);
  615       globals.fsUtils.copyDirectorySync(fatFramework, armFlutterFrameworkDirectory);
  616 
  617       // Create iOS framework.
  618       List<String> lipoCommand = <String>[
  619         ...globals.xcode.xcrunCommand(),
  620         'lipo',
  621         fatFlutterFrameworkBinary.path,
  622         '-remove',
  623         'x86_64',
  624         '-output',
  625         armFlutterFrameworkBinary.path
  626       ];
  627 
  628       RunResult lipoResult = await processUtils.run(
  629         lipoCommand,
  630         allowReentrantFlutter: false,
  631       );
  632 
  633       if (lipoResult.exitCode != 0) {
  634         throwToolExit('Unable to create ARM framework: ${lipoResult.stderr}');
  635       }
  636 
  637       // Create simulator framework.
  638       final Directory simulatorFlutterFrameworkDirectory = simulatorBuildOutput
  639         .childDirectory(frameworkFileName);
  640       final File simulatorFlutterFrameworkBinary = simulatorFlutterFrameworkDirectory
  641         .childFile(frameworkBinaryName);
  642       globals.fsUtils.copyDirectorySync(fatFramework, simulatorFlutterFrameworkDirectory);
  643 
  644       lipoCommand = <String>[
  645         ...globals.xcode.xcrunCommand(),
  646         'lipo',
  647         fatFlutterFrameworkBinary.path,
  648         '-thin',
  649         'x86_64',
  650         '-output',
  651         simulatorFlutterFrameworkBinary.path
  652       ];
  653 
  654       lipoResult = await processUtils.run(
  655         lipoCommand,
  656         allowReentrantFlutter: false,
  657       );
  658 
  659       if (lipoResult.exitCode != 0) {
  660         throwToolExit(
  661             'Unable to create simulator framework: ${lipoResult.stderr}');
  662       }
  663 
  664       // Create XCFramework from iOS and simulator frameworks.
  665       final List<String> xcframeworkCommand = <String>[
  666         ...globals.xcode.xcrunCommand(),
  667         'xcodebuild',
  668         '-create-xcframework',
  669         '-framework', armFlutterFrameworkDirectory.path,
  670         '-framework', simulatorFlutterFrameworkDirectory.path,
  671         '-output', fatFramework.parent
  672             .childFile('$frameworkBinaryName.xcframework')
  673             .path
  674       ];
  675 
  676       final RunResult xcframeworkResult = await processUtils.run(
  677         xcframeworkCommand,
  678         allowReentrantFlutter: false,
  679       );
  680 
  681       if (xcframeworkResult.exitCode != 0) {
  682         throwToolExit(
  683           'Unable to create XCFramework: ${xcframeworkResult.stderr}',
  684         );
  685       }
  686     } finally {
  687       temporaryOutput.deleteSync(recursive: true);
  688     }
  689   }
  690 
  691   Future<void> _produceNonDebugXCFramework(
  692     BuildInfo buildInfo,
  693     Directory fatFramework,
  694     String frameworkBinaryName,
  695   ) async {
  696     // Simulator is only supported in Debug mode.
  697     // "Fat" framework here must only contain arm.
  698     final List<String> xcframeworkCommand = <String>[
  699       ...globals.xcode.xcrunCommand(),
  700       'xcodebuild',
  701       '-create-xcframework',
  702       '-framework', fatFramework.path,
  703       '-output', fatFramework.parent
  704           .childFile('$frameworkBinaryName.xcframework')
  705           .path
  706     ];
  707 
  708     final RunResult xcframeworkResult = await processUtils.run(
  709       xcframeworkCommand,
  710       allowReentrantFlutter: false,
  711     );
  712 
  713     if (xcframeworkResult.exitCode != 0) {
  714       throwToolExit(
  715           'Unable to create XCFramework: ${xcframeworkResult.stderr}');
  716     }
  717   }
  718 }