| 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 'package:meta/meta.dart' ; |
| 6 | import 'package:process/process.dart' ; |
| 7 | |
| 8 | import '../artifacts.dart'; |
| 9 | import '../base/common.dart'; |
| 10 | import '../base/file_system.dart'; |
| 11 | import '../base/io.dart'; |
| 12 | import '../base/logger.dart'; |
| 13 | import '../base/platform.dart'; |
| 14 | import '../base/process.dart'; |
| 15 | import '../build_info.dart'; |
| 16 | import '../build_system/build_system.dart'; |
| 17 | import '../build_system/targets/ios.dart'; |
| 18 | import '../cache.dart'; |
| 19 | import '../darwin/darwin.dart'; |
| 20 | import '../flutter_plugins.dart'; |
| 21 | import '../globals.dart' as globals; |
| 22 | import '../ios/xcodeproj.dart'; |
| 23 | import '../macos/cocoapod_utils.dart'; |
| 24 | import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; |
| 25 | import '../version.dart'; |
| 26 | import 'build.dart'; |
| 27 | |
| 28 | abstract class BuildFrameworkCommand extends BuildSubCommand { |
| 29 | BuildFrameworkCommand({ |
| 30 | // Instantiating FlutterVersion kicks off networking, so delay until it's needed, but allow test injection. |
| 31 | @visibleForTesting FlutterVersion? flutterVersion, |
| 32 | required BuildSystem buildSystem, |
| 33 | required bool verboseHelp, |
| 34 | Cache? cache, |
| 35 | Platform? platform, |
| 36 | required super.logger, |
| 37 | }) : _injectedFlutterVersion = flutterVersion, |
| 38 | _buildSystem = buildSystem, |
| 39 | _injectedCache = cache, |
| 40 | _injectedPlatform = platform, |
| 41 | super(verboseHelp: verboseHelp) { |
| 42 | addTreeShakeIconsFlag(); |
| 43 | usesTargetOption(); |
| 44 | usesPubOption(); |
| 45 | usesDartDefineOption(); |
| 46 | addSplitDebugInfoOption(); |
| 47 | addDartObfuscationOption(); |
| 48 | usesExtraDartFlagOptions(verboseHelp: verboseHelp); |
| 49 | addEnableExperimentation(hide: !verboseHelp); |
| 50 | |
| 51 | argParser |
| 52 | ..addFlag( |
| 53 | 'debug' , |
| 54 | defaultsTo: true, |
| 55 | help: |
| 56 | 'Whether to produce a framework for the debug build configuration. ' |
| 57 | 'By default, all build configurations are built.' , |
| 58 | ) |
| 59 | ..addFlag( |
| 60 | 'profile' , |
| 61 | defaultsTo: true, |
| 62 | help: |
| 63 | 'Whether to produce a framework for the profile build configuration. ' |
| 64 | 'By default, all build configurations are built.' , |
| 65 | ) |
| 66 | ..addFlag( |
| 67 | 'release' , |
| 68 | defaultsTo: true, |
| 69 | help: |
| 70 | 'Whether to produce a framework for the release build configuration. ' |
| 71 | 'By default, all build configurations are built.' , |
| 72 | ) |
| 73 | ..addFlag( |
| 74 | 'cocoapods' , |
| 75 | help: |
| 76 | 'Produce a Flutter.podspec instead of an engine Flutter.xcframework (recommended if host app uses CocoaPods).' , |
| 77 | ) |
| 78 | ..addFlag( |
| 79 | 'plugins' , |
| 80 | defaultsTo: true, |
| 81 | help: |
| 82 | 'Whether to produce frameworks for the plugins. ' |
| 83 | 'This is intended for cases where plugins are already being built separately.' , |
| 84 | ) |
| 85 | ..addFlag( |
| 86 | 'static' , |
| 87 | help: |
| 88 | 'Build plugins as static frameworks. Link on, but do not embed these frameworks in the existing Xcode project.' , |
| 89 | ) |
| 90 | ..addOption( |
| 91 | 'output' , |
| 92 | abbr: 'o' , |
| 93 | valueHelp: 'path/to/directory/' , |
| 94 | help: 'Location to write the frameworks.' , |
| 95 | ) |
| 96 | ..addFlag( |
| 97 | 'force' , |
| 98 | abbr: 'f' , |
| 99 | help: |
| 100 | 'Force Flutter.podspec creation on the master channel. This is only intended for testing the tool itself.' , |
| 101 | hide: !verboseHelp, |
| 102 | ); |
| 103 | } |
| 104 | |
| 105 | final BuildSystem? _buildSystem; |
| 106 | @protected |
| 107 | BuildSystem get buildSystem => _buildSystem ?? globals.buildSystem; |
| 108 | |
| 109 | @protected |
| 110 | Cache get cache => _injectedCache ?? globals.cache; |
| 111 | final Cache? _injectedCache; |
| 112 | |
| 113 | @protected |
| 114 | Platform get platform => _injectedPlatform ?? globals.platform; |
| 115 | final Platform? _injectedPlatform; |
| 116 | |
| 117 | // FlutterVersion.instance kicks off git processing which can sometimes fail, so don't try it until needed. |
| 118 | @protected |
| 119 | FlutterVersion get flutterVersion => _injectedFlutterVersion ?? globals.flutterVersion; |
| 120 | final FlutterVersion? _injectedFlutterVersion; |
| 121 | |
| 122 | Future<List<BuildInfo>> getBuildInfos() async { |
| 123 | return <BuildInfo>[ |
| 124 | if (boolArg('debug' )) await getBuildInfo(forcedBuildMode: BuildMode.debug), |
| 125 | if (boolArg('profile' )) await getBuildInfo(forcedBuildMode: BuildMode.profile), |
| 126 | if (boolArg('release' )) await getBuildInfo(forcedBuildMode: BuildMode.release), |
| 127 | ]; |
| 128 | } |
| 129 | |
| 130 | @override |
| 131 | bool get supported => platform.isMacOS; |
| 132 | |
| 133 | @override |
| 134 | Future<void> validateCommand() async { |
| 135 | await super.validateCommand(); |
| 136 | if (!supported) { |
| 137 | throwToolExit('Building frameworks for iOS is only supported on the Mac.' ); |
| 138 | } |
| 139 | |
| 140 | if ((await getBuildInfos()).isEmpty) { |
| 141 | throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.' ); |
| 142 | } |
| 143 | |
| 144 | if (!boolArg('plugins' ) && boolArg('static' )) { |
| 145 | throwToolExit('--static cannot be used with the --no-plugins flag' ); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | static Future<void> produceXCFramework( |
| 150 | Iterable<Directory> frameworks, |
| 151 | String frameworkBinaryName, |
| 152 | Directory outputDirectory, |
| 153 | ProcessManager processManager, |
| 154 | ) async { |
| 155 | final xcframeworkCommand = <String>[ |
| 156 | 'xcrun' , |
| 157 | 'xcodebuild' , |
| 158 | '-create-xcframework' , |
| 159 | for (final Directory framework in frameworks) ...<String>[ |
| 160 | '-framework' , |
| 161 | framework.path, |
| 162 | ...framework.parent |
| 163 | .listSync() |
| 164 | .where( |
| 165 | (FileSystemEntity entity) => |
| 166 | entity.basename.endsWith('dSYM' ) && !entity.basename.startsWith('Flutter' ), |
| 167 | ) |
| 168 | .map((FileSystemEntity entity) => <String>['-debug-symbols' , entity.path]) |
| 169 | .expand<String>((List<String> parameter) => parameter), |
| 170 | ], |
| 171 | '-output' , |
| 172 | outputDirectory.childDirectory(' $frameworkBinaryName.xcframework' ).path, |
| 173 | ]; |
| 174 | |
| 175 | final ProcessResult xcframeworkResult = await processManager.run(xcframeworkCommand); |
| 176 | |
| 177 | if (xcframeworkResult.exitCode != 0) { |
| 178 | throwToolExit( |
| 179 | 'Unable to create $frameworkBinaryName.xcframework: ${xcframeworkResult.stderr}' , |
| 180 | ); |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | /// Produces a .framework for integration into a host iOS app. The .framework |
| 186 | /// contains the Flutter engine and framework code as well as plugins. It can |
| 187 | /// be integrated into plain Xcode projects without using or other package |
| 188 | /// managers. |
| 189 | class BuildIOSFrameworkCommand extends BuildFrameworkCommand { |
| 190 | BuildIOSFrameworkCommand({ |
| 191 | required super.logger, |
| 192 | super.flutterVersion, |
| 193 | required super.buildSystem, |
| 194 | required bool verboseHelp, |
| 195 | super.cache, |
| 196 | super.platform, |
| 197 | }) : super(verboseHelp: verboseHelp) { |
| 198 | usesFlavorOption(); |
| 199 | |
| 200 | argParser |
| 201 | ..addFlag( |
| 202 | 'universal' , |
| 203 | help: '(deprecated) Produce universal frameworks that include all valid architectures.' , |
| 204 | hide: !verboseHelp, |
| 205 | ) |
| 206 | ..addFlag( |
| 207 | 'xcframework' , |
| 208 | help: 'Produce xcframeworks that include all valid architectures.' , |
| 209 | negatable: false, |
| 210 | defaultsTo: true, |
| 211 | hide: !verboseHelp, |
| 212 | ); |
| 213 | } |
| 214 | |
| 215 | @override |
| 216 | final name = 'ios-framework' ; |
| 217 | |
| 218 | @override |
| 219 | final description = |
| 220 | 'Produces .xcframeworks for a Flutter project ' |
| 221 | 'and its plugins for integration into existing, plain iOS Xcode projects.\n' |
| 222 | 'This can only be run on macOS hosts.' ; |
| 223 | |
| 224 | @override |
| 225 | Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{ |
| 226 | DevelopmentArtifact.iOS, |
| 227 | }; |
| 228 | |
| 229 | @override |
| 230 | Future<void> validateCommand() async { |
| 231 | await super.validateCommand(); |
| 232 | |
| 233 | if (boolArg('universal' )) { |
| 234 | throwToolExit('--universal has been deprecated, only XCFrameworks are supported.' ); |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | @override |
| 239 | bool get regeneratePlatformSpecificToolingDuringVerify => false; |
| 240 | |
| 241 | @override |
| 242 | Future<FlutterCommandResult> runCommand() async { |
| 243 | final String outputArgument = |
| 244 | stringArg('output' ) ?? |
| 245 | globals.fs.path.join(globals.fs.currentDirectory.path, 'build' , 'ios' , 'framework' ); |
| 246 | |
| 247 | if (outputArgument.isEmpty) { |
| 248 | throwToolExit('--output is required.' ); |
| 249 | } |
| 250 | |
| 251 | if (!project.ios.existsSync()) { |
| 252 | throwToolExit('Project does not support iOS' ); |
| 253 | } |
| 254 | |
| 255 | final Directory outputDirectory = globals.fs.directory( |
| 256 | globals.fs.path.absolute(globals.fs.path.normalize(outputArgument)), |
| 257 | ); |
| 258 | final List<BuildInfo> buildInfos = await getBuildInfos(); |
| 259 | for (final buildInfo in buildInfos) { |
| 260 | // Create the build-mode specific metadata. |
| 261 | // |
| 262 | // This normally would be done in the verifyAndRun step of FlutterCommand, but special "meta" |
| 263 | // build commands (like flutter build ios-framework) make multiple builds, and do not have a |
| 264 | // single "buildInfo", so the step has to be done manually for each build. |
| 265 | // |
| 266 | // See regeneratePlatformSpecificToolingDurifyVerify. |
| 267 | await regeneratePlatformSpecificToolingIfApplicable( |
| 268 | project, |
| 269 | releaseMode: buildInfo.mode.isRelease, |
| 270 | ); |
| 271 | |
| 272 | final String? productBundleIdentifier = await project.ios.productBundleIdentifier(buildInfo); |
| 273 | globals.printStatus( |
| 274 | 'Building frameworks for $productBundleIdentifier in ${buildInfo.mode.cliName} mode...' , |
| 275 | ); |
| 276 | |
| 277 | final String xcodeBuildConfiguration = buildInfo.mode.uppercaseName; |
| 278 | final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration); |
| 279 | |
| 280 | if (modeDirectory.existsSync()) { |
| 281 | modeDirectory.deleteSync(recursive: true); |
| 282 | } |
| 283 | |
| 284 | if (boolArg('cocoapods' )) { |
| 285 | produceFlutterPodspec(buildInfo.mode, modeDirectory, force: boolArg('force' )); |
| 286 | } else { |
| 287 | // Copy Flutter.xcframework. |
| 288 | await _produceFlutterFramework(buildInfo, modeDirectory); |
| 289 | } |
| 290 | |
| 291 | // Build aot, create module.framework and copy. |
| 292 | final Directory iPhoneBuildOutput = modeDirectory.childDirectory( |
| 293 | XcodeSdk.IPhoneOS.platformName, |
| 294 | ); |
| 295 | final Directory simulatorBuildOutput = modeDirectory.childDirectory( |
| 296 | XcodeSdk.IPhoneSimulator.platformName, |
| 297 | ); |
| 298 | await _produceAppFramework(buildInfo, modeDirectory, iPhoneBuildOutput, simulatorBuildOutput); |
| 299 | |
| 300 | // Build and copy plugins. |
| 301 | await processPodsIfNeeded( |
| 302 | project.ios, |
| 303 | getIosBuildDirectory(), |
| 304 | buildInfo.mode, |
| 305 | forceCocoaPodsOnly: true, |
| 306 | ); |
| 307 | if (boolArg('plugins' ) && hasPlugins(project)) { |
| 308 | await _producePlugins( |
| 309 | buildInfo.mode, |
| 310 | xcodeBuildConfiguration, |
| 311 | iPhoneBuildOutput, |
| 312 | simulatorBuildOutput, |
| 313 | modeDirectory, |
| 314 | ); |
| 315 | } |
| 316 | |
| 317 | final Status status = globals.logger.startProgress( |
| 318 | ' └─Moving to ${globals.fs.path.relative(modeDirectory.path)}' , |
| 319 | ); |
| 320 | |
| 321 | // Copy the native assets. The native assets have already been signed in |
| 322 | // buildNativeAssetsMacOS. |
| 323 | final Directory nativeAssetsDirectory = globals.fs |
| 324 | .directory(getBuildDirectory()) |
| 325 | .childDirectory('native_assets/ios/' ); |
| 326 | if (await nativeAssetsDirectory.exists()) { |
| 327 | final ProcessResult rsyncResult = await globals.processManager.run(<Object>[ |
| 328 | 'rsync' , |
| 329 | '-av' , |
| 330 | '--filter' , |
| 331 | '- .DS_Store' , |
| 332 | '--filter' , |
| 333 | '- native_assets.yaml' , |
| 334 | '--filter' , |
| 335 | '- native_assets.json' , |
| 336 | nativeAssetsDirectory.path, |
| 337 | modeDirectory.path, |
| 338 | ]); |
| 339 | if (rsyncResult.exitCode != 0) { |
| 340 | throwToolExit('Failed to copy native assets:\n ${rsyncResult.stderr}' ); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | try { |
| 345 | // Delete the intermediaries since they would have been copied into our |
| 346 | // output frameworks. |
| 347 | if (iPhoneBuildOutput.existsSync()) { |
| 348 | iPhoneBuildOutput.deleteSync(recursive: true); |
| 349 | } |
| 350 | if (simulatorBuildOutput.existsSync()) { |
| 351 | simulatorBuildOutput.deleteSync(recursive: true); |
| 352 | } |
| 353 | } finally { |
| 354 | status.stop(); |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | globals.printStatus('Frameworks written to ${outputDirectory.path}.' ); |
| 359 | |
| 360 | if (!project.isModule && hasPlugins(project)) { |
| 361 | // Apps do not generate a FlutterPluginRegistrant.framework. Users will need |
| 362 | // to copy the GeneratedPluginRegistrant class to their project manually. |
| 363 | final File pluginRegistrantHeader = project.ios.pluginRegistrantHeader; |
| 364 | final File pluginRegistrantImplementation = project.ios.pluginRegistrantImplementation; |
| 365 | pluginRegistrantHeader.copySync( |
| 366 | outputDirectory.childFile(pluginRegistrantHeader.basename).path, |
| 367 | ); |
| 368 | pluginRegistrantImplementation.copySync( |
| 369 | outputDirectory.childFile(pluginRegistrantImplementation.basename).path, |
| 370 | ); |
| 371 | globals.printStatus( |
| 372 | '\nCopy the ${globals.fs.path.basenameWithoutExtension(pluginRegistrantHeader.path)} class into your project.\n' |
| 373 | 'See https://flutter.dev/to/ios-create-flutter-engine for more information.', |
| 374 | ); |
| 375 | } |
| 376 | |
| 377 | if (buildInfos.any((BuildInfo info) => info.isDebug)) { |
| 378 | // Add-to-App must manually add the LLDB Init File to their native Xcode |
| 379 | // project, so provide the files and instructions. |
| 380 | final File lldbInitSourceFile = project.ios.lldbInitFile; |
| 381 | final File lldbInitTargetFile = outputDirectory.childFile(lldbInitSourceFile.basename); |
| 382 | final File lldbHelperPythonFile = project.ios.lldbHelperPythonFile; |
| 383 | |
| 384 | if (!lldbInitTargetFile.existsSync()) { |
| 385 | // If LLDB is being added to the output, print a warning with instructions on how to add. |
| 386 | globals.printWarning( |
| 387 | 'Debugging Flutter on new iOS versions requires an LLDB Init File. To ' |
| 388 | 'ensure debug mode works, please complete instructions found in ' |
| 389 | '"Embed a Flutter module in your iOS app > Use frameworks > Set LLDB Init File" ' |
| 390 | 'section of https://docs.flutter.dev/to/ios-add-to-app-embed-setup.', |
| 391 | );
|
| 392 | }
|
| 393 | lldbInitSourceFile.copySync(lldbInitTargetFile.path);
|
| 394 | lldbHelperPythonFile.copySync(outputDirectory.childFile(lldbHelperPythonFile.basename).path);
|
| 395 | }
|
| 396 |
|
| 397 | return FlutterCommandResult.success();
|
| 398 | }
|
| 399 |
|
| 400 | /// Create podspec that will download and unzip remote engine assets so host apps can leverage CocoaPods
|
| 401 | /// vendored framework caching.
|
| 402 | @visibleForTesting
|
| 403 | void produceFlutterPodspec(BuildMode mode, Directory modeDirectory, {bool force = false}) {
|
| 404 | final Status status = globals.logger.startProgress(' ├─Creating Flutter.podspec...' );
|
| 405 | try {
|
| 406 | final GitTagVersion gitTagVersion = flutterVersion.gitTagVersion;
|
| 407 | if (!force &&
|
| 408 | (gitTagVersion.x == null ||
|
| 409 | gitTagVersion.y == null ||
|
| 410 | gitTagVersion.z == null ||
|
| 411 | gitTagVersion.commits != 0)) {
|
| 412 | throwToolExit(
|
| 413 | '--cocoapods is only supported on the beta or stable channel. Detected version is ${flutterVersion.frameworkVersion}' ,
|
| 414 | );
|
| 415 | }
|
| 416 |
|
| 417 | // Podspecs use semantic versioning, which don't support hotfixes.
|
| 418 | // Fake out a semantic version with major.minor.(patch * 100) + hotfix.
|
| 419 | // A real increasing version is required to prompt CocoaPods to fetch
|
| 420 | // new artifacts when the source URL changes.
|
| 421 | final int minorHotfixVersion = (gitTagVersion.z ?? 0) * 100 + (gitTagVersion.hotfix ?? 0);
|
| 422 |
|
| 423 | final File license = cache.getLicenseFile();
|
| 424 | if (!license.existsSync()) {
|
| 425 | throwToolExit('Could not find license at ${license.path}' );
|
| 426 | }
|
| 427 | final String licenseSource = license.readAsStringSync();
|
| 428 | final String artifactsMode = FlutterDarwinPlatform.ios.artifactName(mode);
|
| 429 |
|
| 430 | final podspecContents =
|
| 431 | '''
|
| 432 | Pod::Spec.new do |s|
|
| 433 | s.name = ' ${FlutterDarwinPlatform.ios.binaryName}'
|
| 434 | s.version = ' ${gitTagVersion.x}. ${gitTagVersion.y}. $minorHotfixVersion' # ${flutterVersion.frameworkVersion}
|
| 435 | s.summary = 'A UI toolkit for beautiful and fast apps.'
|
| 436 | s.description = <<-DESC
|
| 437 | Flutter is Google's UI toolkit for building beautiful, fast apps for mobile, web, desktop, and embedded devices from a single codebase.
|
| 438 | This pod vends the iOS Flutter engine framework. It is compatible with application frameworks created with this version of the engine and tools.
|
| 439 | The pod version matches Flutter version major.minor.(patch * 100) + hotfix.
|
| 440 | DESC
|
| 441 | s.homepage = 'https://flutter.dev'
|
| 442 | s.license = { :type => 'BSD', :text => <<-LICENSE
|
| 443 | $licenseSource
|
| 444 | LICENSE
|
| 445 | }
|
| 446 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
|
| 447 | s.source = { :http => ' ${cache.storageBaseUrl}/flutter_infra_release/flutter/ ${cache.engineRevision}/ $artifactsMode/ ${FlutterDarwinPlatform.ios.artifactZip}' }
|
| 448 | s.documentation_url = 'https://docs.flutter.dev'
|
| 449 | s.platform = :ios, ' ${FlutterDarwinPlatform.ios.deploymentTarget()}'
|
| 450 | s.vendored_frameworks = ' ${FlutterDarwinPlatform.ios.xcframeworkName}'
|
| 451 | end
|
| 452 | ''' ;
|
| 453 |
|
| 454 | final File podspec = modeDirectory.childFile('Flutter.podspec' )..createSync(recursive: true);
|
| 455 | podspec.writeAsStringSync(podspecContents);
|
| 456 | } finally {
|
| 457 | status.stop();
|
| 458 | }
|
| 459 | }
|
| 460 |
|
| 461 | Future<void> _produceFlutterFramework(BuildInfo buildInfo, Directory modeDirectory) async {
|
| 462 | final Status status = globals.logger.startProgress(' ├─Copying Flutter.xcframework...' );
|
| 463 | final String engineCacheFlutterFrameworkDirectory = globals.artifacts!.getArtifactPath(
|
| 464 | Artifact.flutterXcframework,
|
| 465 | platform: TargetPlatform.ios,
|
| 466 | mode: buildInfo.mode,
|
| 467 | );
|
| 468 | final String flutterFrameworkFileName = globals.fs.path.basename(
|
| 469 | engineCacheFlutterFrameworkDirectory,
|
| 470 | );
|
| 471 | final Directory flutterFrameworkCopy = modeDirectory.childDirectory(flutterFrameworkFileName);
|
| 472 |
|
| 473 | try {
|
| 474 | // Copy xcframework engine cache framework to mode directory.
|
| 475 | copyDirectory(
|
| 476 | globals.fs.directory(engineCacheFlutterFrameworkDirectory),
|
| 477 | flutterFrameworkCopy,
|
| 478 | );
|
| 479 | } finally {
|
| 480 | status.stop();
|
| 481 | }
|
| 482 | }
|
| 483 |
|
| 484 | Future<void> _produceAppFramework(
|
| 485 | BuildInfo buildInfo,
|
| 486 | Directory outputDirectory,
|
| 487 | Directory iPhoneBuildOutput,
|
| 488 | Directory simulatorBuildOutput,
|
| 489 | ) async {
|
| 490 | const appFrameworkName = 'App.framework' ;
|
| 491 | final Status status = globals.logger.startProgress(' ├─Building App.xcframework...' );
|
| 492 | final frameworks = <Directory>[];
|
| 493 |
|
| 494 | try {
|
| 495 | for (final EnvironmentType sdkType in EnvironmentType.values) {
|
| 496 | final Directory outputBuildDirectory = switch (sdkType) {
|
| 497 | EnvironmentType.physical => iPhoneBuildOutput,
|
| 498 | EnvironmentType.simulator => simulatorBuildOutput,
|
| 499 | };
|
| 500 | frameworks.add(outputBuildDirectory.childDirectory(appFrameworkName));
|
| 501 | final environment = Environment(
|
| 502 | projectDir: globals.fs.currentDirectory,
|
| 503 | packageConfigPath: packageConfigPath(),
|
| 504 | outputDir: outputBuildDirectory,
|
| 505 | buildDir: project.dartTool.childDirectory('flutter_build' ),
|
| 506 | cacheDir: globals.cache.getRoot(),
|
| 507 | flutterRootDir: globals.fs.directory(Cache.flutterRoot),
|
| 508 | defines: <String, String>{
|
| 509 | kTargetFile: targetFile,
|
| 510 | kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ios),
|
| 511 | kIosArchs: defaultIOSArchsForEnvironment(
|
| 512 | sdkType,
|
| 513 | globals.artifacts!,
|
| 514 | ).map((DarwinArch e) => e.name).join(' ' ),
|
| 515 | kSdkRoot: await globals.xcode!.sdkLocation(sdkType),
|
| 516 | ...buildInfo.toBuildSystemEnvironment(),
|
| 517 | },
|
| 518 | artifacts: globals.artifacts!,
|
| 519 | fileSystem: globals.fs,
|
| 520 | logger: globals.logger,
|
| 521 | processManager: globals.processManager,
|
| 522 | platform: globals.platform,
|
| 523 | analytics: globals.analytics,
|
| 524 | engineVersion: globals.artifacts!.usesLocalArtifacts
|
| 525 | ? null
|
| 526 | : globals.flutterVersion.engineRevision,
|
| 527 | generateDartPluginRegistry: true,
|
| 528 | );
|
| 529 | Target target;
|
| 530 | // Always build debug for simulator.
|
| 531 | if (buildInfo.isDebug || sdkType == EnvironmentType.simulator) {
|
| 532 | target = const DebugIosApplicationBundle();
|
| 533 | } else if (buildInfo.isProfile) {
|
| 534 | target = const ProfileIosApplicationBundle();
|
| 535 | } else {
|
| 536 | target = const ReleaseIosApplicationBundle();
|
| 537 | }
|
| 538 | final BuildResult result = await buildSystem.build(target, environment);
|
| 539 | if (!result.success) {
|
| 540 | for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
| 541 | globals.printError(measurement.exception.toString());
|
| 542 | }
|
| 543 | throwToolExit('The App.xcframework build failed.' );
|
| 544 | }
|
| 545 | }
|
| 546 | } finally {
|
| 547 | status.stop();
|
| 548 | }
|
| 549 |
|
| 550 | await BuildFrameworkCommand.produceXCFramework(
|
| 551 | frameworks,
|
| 552 | 'App' ,
|
| 553 | outputDirectory,
|
| 554 | globals.processManager,
|
| 555 | );
|
| 556 | }
|
| 557 |
|
| 558 | Future<void> _producePlugins(
|
| 559 | BuildMode mode,
|
| 560 | String xcodeBuildConfiguration,
|
| 561 | Directory iPhoneBuildOutput,
|
| 562 | Directory simulatorBuildOutput,
|
| 563 | Directory modeDirectory,
|
| 564 | ) async {
|
| 565 | final Status status = globals.logger.startProgress(' ├─Building plugins...' );
|
| 566 | try {
|
| 567 | var pluginsBuildCommand = <String>[
|
| 568 | ...globals.xcode!.xcrunCommand(),
|
| 569 | 'xcodebuild' ,
|
| 570 | '-alltargets' ,
|
| 571 | '-sdk' ,
|
| 572 | XcodeSdk.IPhoneOS.platformName,
|
| 573 | '-configuration' ,
|
| 574 | xcodeBuildConfiguration,
|
| 575 | 'SYMROOT= ${iPhoneBuildOutput.path}' ,
|
| 576 | 'ONLY_ACTIVE_ARCH=NO' , // No device targeted, so build all valid architectures.
|
| 577 | 'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' ,
|
| 578 | if (boolArg('static' )) 'MACH_O_TYPE=staticlib' ,
|
| 579 | ];
|
| 580 |
|
| 581 | RunResult buildPluginsResult = await globals.processUtils.run(
|
| 582 | pluginsBuildCommand,
|
| 583 | workingDirectory: project.ios.hostAppRoot.childDirectory('Pods' ).path,
|
| 584 | );
|
| 585 |
|
| 586 | if (buildPluginsResult.exitCode != 0) {
|
| 587 | throwToolExit('Unable to build plugin frameworks: ${buildPluginsResult.stderr}' );
|
| 588 | }
|
| 589 |
|
| 590 | // Always build debug for simulator.
|
| 591 | final String simulatorConfiguration = BuildMode.debug.uppercaseName;
|
| 592 | pluginsBuildCommand = <String>[
|
| 593 | ...globals.xcode!.xcrunCommand(),
|
| 594 | 'xcodebuild' ,
|
| 595 | '-alltargets' ,
|
| 596 | '-sdk' ,
|
| 597 | XcodeSdk.IPhoneSimulator.platformName,
|
| 598 | '-configuration' ,
|
| 599 | simulatorConfiguration,
|
| 600 | 'SYMROOT= ${simulatorBuildOutput.path}' ,
|
| 601 | 'ONLY_ACTIVE_ARCH=NO' , // No device targeted, so build all valid architectures.
|
| 602 | 'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' ,
|
| 603 | if (boolArg('static' )) 'MACH_O_TYPE=staticlib' ,
|
| 604 | ];
|
| 605 |
|
| 606 | buildPluginsResult = await globals.processUtils.run(
|
| 607 | pluginsBuildCommand,
|
| 608 | workingDirectory: project.ios.hostAppRoot.childDirectory('Pods' ).path,
|
| 609 | );
|
| 610 |
|
| 611 | if (buildPluginsResult.exitCode != 0) {
|
| 612 | throwToolExit(
|
| 613 | 'Unable to build plugin frameworks for simulator: ${buildPluginsResult.stderr}' ,
|
| 614 | );
|
| 615 | }
|
| 616 |
|
| 617 | final Directory iPhoneBuildConfiguration = iPhoneBuildOutput.childDirectory(
|
| 618 | ' $xcodeBuildConfiguration- ${XcodeSdk.IPhoneOS.platformName}' ,
|
| 619 | );
|
| 620 | final Directory simulatorBuildConfiguration = simulatorBuildOutput.childDirectory(
|
| 621 | ' $simulatorConfiguration- ${XcodeSdk.IPhoneSimulator.platformName}' ,
|
| 622 | );
|
| 623 |
|
| 624 | final Iterable<Directory> products = iPhoneBuildConfiguration
|
| 625 | .listSync(followLinks: false)
|
| 626 | .whereType<Directory>();
|
| 627 | for (final builtProduct in products) {
|
| 628 | for (final FileSystemEntity podProduct in builtProduct.listSync(followLinks: false)) {
|
| 629 | final String podFrameworkName = podProduct.basename;
|
| 630 | if (globals.fs.path.extension(podFrameworkName) != '.framework' ) {
|
| 631 | continue;
|
| 632 | }
|
| 633 | final String binaryName = globals.fs.path.basenameWithoutExtension(podFrameworkName);
|
| 634 |
|
| 635 | final frameworks = <Directory>[
|
| 636 | podProduct as Directory,
|
| 637 | simulatorBuildConfiguration
|
| 638 | .childDirectory(builtProduct.basename)
|
| 639 | .childDirectory(podFrameworkName),
|
| 640 | ];
|
| 641 |
|
| 642 | await BuildFrameworkCommand.produceXCFramework(
|
| 643 | frameworks,
|
| 644 | binaryName,
|
| 645 | modeDirectory,
|
| 646 | globals.processManager,
|
| 647 | );
|
| 648 | }
|
| 649 | }
|
| 650 | } finally {
|
| 651 | status.stop();
|
| 652 | }
|
| 653 | }
|
| 654 | }
|
| 655 |
|