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 |
|