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
5import 'package:meta/meta.dart';
6import 'package:unified_analytics/unified_analytics.dart';
7
8import '../../artifacts.dart';
9import '../../base/build.dart';
10import '../../base/common.dart';
11import '../../base/file_system.dart';
12import '../../base/io.dart';
13import '../../base/process.dart';
14import '../../base/version.dart';
15import '../../build_info.dart';
16import '../../devfs.dart';
17import '../../globals.dart' as globals;
18import '../../ios/mac.dart';
19import '../../macos/xcode.dart';
20import '../../project.dart';
21import '../build_system.dart';
22import '../depfile.dart';
23import '../exceptions.dart';
24import '../tools/shader_compiler.dart';
25import 'assets.dart';
26import 'common.dart';
27import 'icon_tree_shaker.dart';
28import 'native_assets.dart';
29
30/// Supports compiling a dart kernel file to an assembly file.
31///
32/// If more than one iOS arch is provided, then this rule will
33/// produce a universal binary.
34abstract class AotAssemblyBase extends Target {
35 const AotAssemblyBase();
36
37 @override
38 String get analyticsName => 'ios_aot';
39
40 @override
41 Future<void> build(Environment environment) async {
42 final AOTSnapshotter snapshotter = AOTSnapshotter(
43 fileSystem: environment.fileSystem,
44 logger: environment.logger,
45 xcode: globals.xcode!,
46 artifacts: environment.artifacts,
47 processManager: environment.processManager,
48 );
49 final String buildOutputPath = environment.buildDir.path;
50 final String? environmentBuildMode = environment.defines[kBuildMode];
51 if (environmentBuildMode == null) {
52 throw MissingDefineException(kBuildMode, 'aot_assembly');
53 }
54 final String? environmentTargetPlatform = environment.defines[kTargetPlatform];
55 if (environmentTargetPlatform == null) {
56 throw MissingDefineException(kTargetPlatform, 'aot_assembly');
57 }
58 final String? sdkRoot = environment.defines[kSdkRoot];
59 if (sdkRoot == null) {
60 throw MissingDefineException(kSdkRoot, 'aot_assembly');
61 }
62
63 final List<String> extraGenSnapshotOptions = decodeCommaSeparated(
64 environment.defines,
65 kExtraGenSnapshotOptions,
66 );
67 final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode);
68 final TargetPlatform targetPlatform = getTargetPlatformForName(environmentTargetPlatform);
69 final String? splitDebugInfo = environment.defines[kSplitDebugInfo];
70 final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
71 final List<DarwinArch> darwinArchs =
72 environment.defines[kIosArchs]?.split(' ').map(getIOSArchForName).toList() ??
73 <DarwinArch>[DarwinArch.arm64];
74 if (targetPlatform != TargetPlatform.ios) {
75 throw Exception('aot_assembly is only supported for iOS applications.');
76 }
77
78 final EnvironmentType? environmentType = environmentTypeFromSdkroot(
79 sdkRoot,
80 environment.fileSystem,
81 );
82 if (environmentType == EnvironmentType.simulator) {
83 throw Exception(
84 'release/profile builds are only supported for physical devices. '
85 'attempted to build for simulator.',
86 );
87 }
88 final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory];
89
90 // If we're building multiple iOS archs the binaries need to be lipo'd
91 // together.
92 final List<Future<int>> pending = <Future<int>>[];
93 for (final DarwinArch darwinArch in darwinArchs) {
94 final List<String> archExtraGenSnapshotOptions = List<String>.of(extraGenSnapshotOptions);
95 if (codeSizeDirectory != null) {
96 final File codeSizeFile = environment.fileSystem
97 .directory(codeSizeDirectory)
98 .childFile('snapshot.${darwinArch.name}.json');
99 final File precompilerTraceFile = environment.fileSystem
100 .directory(codeSizeDirectory)
101 .childFile('trace.${darwinArch.name}.json');
102 archExtraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
103 archExtraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
104 }
105 pending.add(
106 snapshotter.build(
107 platform: targetPlatform,
108 buildMode: buildMode,
109 mainPath: environment.buildDir.childFile('app.dill').path,
110 outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name),
111 darwinArch: darwinArch,
112 sdkRoot: sdkRoot,
113 quiet: true,
114 splitDebugInfo: splitDebugInfo,
115 dartObfuscation: dartObfuscation,
116 extraGenSnapshotOptions: archExtraGenSnapshotOptions,
117 ),
118 );
119 }
120 final List<int> results = await Future.wait(pending);
121 if (results.any((int result) => result != 0)) {
122 throw Exception('AOT snapshotter exited with code ${results.join()}');
123 }
124
125 // Combine the app lib into a fat framework.
126 await Lipo.create(
127 environment,
128 darwinArchs,
129 relativePath: 'App.framework/App',
130 inputDir: buildOutputPath,
131 );
132
133 // And combine the dSYM for each architecture too, if it was created.
134 await Lipo.create(
135 environment,
136 darwinArchs,
137 relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App',
138 inputDir: buildOutputPath,
139 // Don't fail if the dSYM wasn't created (i.e. during a debug build).
140 skipMissingInputs: true,
141 );
142 }
143}
144
145/// Generate an assembly target from a dart kernel file in release mode.
146class AotAssemblyRelease extends AotAssemblyBase {
147 const AotAssemblyRelease();
148
149 @override
150 String get name => 'aot_assembly_release';
151
152 @override
153 List<Source> get inputs => const <Source>[
154 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
155 Source.pattern('{BUILD_DIR}/app.dill'),
156 Source.artifact(Artifact.engineDartBinary),
157 Source.artifact(Artifact.skyEnginePath),
158 // TODO(zanderso): cannot reference gen_snapshot with artifacts since
159 // it resolves to a file (ios/gen_snapshot) that never exists. This was
160 // split into gen_snapshot_arm64 and gen_snapshot_armv7.
161 // Source.artifact(Artifact.genSnapshot,
162 // platform: TargetPlatform.ios,
163 // mode: BuildMode.release,
164 // ),
165 ];
166
167 @override
168 List<Source> get outputs => const <Source>[Source.pattern('{OUTPUT_DIR}/App.framework/App')];
169
170 @override
171 List<Target> get dependencies => const <Target>[ReleaseUnpackIOS(), KernelSnapshot()];
172}
173
174/// Generate an assembly target from a dart kernel file in profile mode.
175class AotAssemblyProfile extends AotAssemblyBase {
176 const AotAssemblyProfile();
177
178 @override
179 String get name => 'aot_assembly_profile';
180
181 @override
182 List<Source> get inputs => const <Source>[
183 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
184 Source.pattern('{BUILD_DIR}/app.dill'),
185 Source.artifact(Artifact.engineDartBinary),
186 Source.artifact(Artifact.skyEnginePath),
187 // TODO(zanderso): cannot reference gen_snapshot with artifacts since
188 // it resolves to a file (ios/gen_snapshot) that never exists. This was
189 // split into gen_snapshot_arm64 and gen_snapshot_armv7.
190 // Source.artifact(Artifact.genSnapshot,
191 // platform: TargetPlatform.ios,
192 // mode: BuildMode.profile,
193 // ),
194 ];
195
196 @override
197 List<Source> get outputs => const <Source>[Source.pattern('{OUTPUT_DIR}/App.framework/App')];
198
199 @override
200 List<Target> get dependencies => const <Target>[ProfileUnpackIOS(), KernelSnapshot()];
201}
202
203/// Create a trivial App.framework file for debug iOS builds.
204class DebugUniversalFramework extends Target {
205 const DebugUniversalFramework();
206
207 @override
208 String get name => 'debug_universal_framework';
209
210 @override
211 List<Target> get dependencies => const <Target>[DebugUnpackIOS(), KernelSnapshot()];
212
213 @override
214 List<Source> get inputs => const <Source>[
215 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
216 ];
217
218 @override
219 List<Source> get outputs => const <Source>[Source.pattern('{BUILD_DIR}/App.framework/App')];
220
221 @override
222 Future<void> build(Environment environment) async {
223 final String? sdkRoot = environment.defines[kSdkRoot];
224 if (sdkRoot == null) {
225 throw MissingDefineException(kSdkRoot, name);
226 }
227
228 // Generate a trivial App.framework.
229 final Set<String>? iosArchNames = environment.defines[kIosArchs]?.split(' ').toSet();
230 final File output = environment.buildDir.childDirectory('App.framework').childFile('App');
231 environment.buildDir.createSync(recursive: true);
232 await _createStubAppFramework(output, environment, iosArchNames, sdkRoot);
233 }
234}
235
236/// Copy the iOS framework to the correct copy dir by invoking 'rsync'.
237///
238/// This class is abstract to share logic between the three concrete
239/// implementations. The shelling out is done to avoid complications with
240/// preserving special files (e.g., symbolic links) in the framework structure.
241abstract class UnpackIOS extends Target {
242 const UnpackIOS();
243
244 @override
245 List<Source> get inputs => <Source>[
246 const Source.pattern(
247 '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart',
248 ),
249 Source.artifact(Artifact.flutterXcframework, platform: TargetPlatform.ios, mode: buildMode),
250 ];
251
252 @override
253 List<Source> get outputs => const <Source>[
254 Source.pattern('{OUTPUT_DIR}/Flutter.framework/Flutter'),
255 ];
256
257 @override
258 List<Target> get dependencies => <Target>[];
259
260 @visibleForOverriding
261 BuildMode get buildMode;
262
263 @override
264 Future<void> build(Environment environment) async {
265 final String? sdkRoot = environment.defines[kSdkRoot];
266 if (sdkRoot == null) {
267 throw MissingDefineException(kSdkRoot, name);
268 }
269 final String? archs = environment.defines[kIosArchs];
270 if (archs == null) {
271 throw MissingDefineException(kIosArchs, name);
272 }
273 await _copyFramework(environment, sdkRoot);
274
275 final File frameworkBinary = environment.outputDir
276 .childDirectory('Flutter.framework')
277 .childFile('Flutter');
278 final String frameworkBinaryPath = frameworkBinary.path;
279 if (!await frameworkBinary.exists()) {
280 throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
281 }
282 await _thinFramework(environment, frameworkBinaryPath, archs);
283 await _signFramework(environment, frameworkBinary, buildMode);
284 }
285
286 Future<void> _copyFramework(Environment environment, String sdkRoot) async {
287 // Copy Flutter framework.
288 final EnvironmentType? environmentType = environmentTypeFromSdkroot(
289 sdkRoot,
290 environment.fileSystem,
291 );
292 final String basePath = environment.artifacts.getArtifactPath(
293 Artifact.flutterFramework,
294 platform: TargetPlatform.ios,
295 mode: buildMode,
296 environmentType: environmentType,
297 );
298
299 final ProcessResult result = await environment.processManager.run(<String>[
300 'rsync',
301 '-av',
302 '--delete',
303 '--filter',
304 '- .DS_Store/',
305 '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
306 basePath,
307 environment.outputDir.path,
308 ]);
309 if (result.exitCode != 0) {
310 throw Exception(
311 'Failed to copy framework (exit ${result.exitCode}:\n'
312 '${result.stdout}\n---\n${result.stderr}',
313 );
314 }
315
316 // Copy Flutter framework dSYM (debug symbol) bundle, if present.
317 final Directory frameworkDsym = environment.fileSystem.directory(
318 environment.artifacts.getArtifactPath(
319 Artifact.flutterFrameworkDsym,
320 platform: TargetPlatform.ios,
321 mode: buildMode,
322 environmentType: environmentType,
323 ),
324 );
325 if (frameworkDsym.existsSync()) {
326 final ProcessResult result = await environment.processManager.run(<String>[
327 'rsync',
328 '-av',
329 '--delete',
330 '--filter',
331 '- .DS_Store/',
332 '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
333 frameworkDsym.path,
334 environment.outputDir.path,
335 ]);
336 if (result.exitCode != 0) {
337 throw Exception(
338 'Failed to copy framework dSYM (exit ${result.exitCode}:\n'
339 '${result.stdout}\n---\n${result.stderr}',
340 );
341 }
342 }
343 }
344
345 /// Destructively thin Flutter.framework to include only the specified architectures.
346 Future<void> _thinFramework(
347 Environment environment,
348 String frameworkBinaryPath,
349 String archs,
350 ) async {
351 final List<String> archList = archs.split(' ').toList();
352 final ProcessResult infoResult = await environment.processManager.run(<String>[
353 'lipo',
354 '-info',
355 frameworkBinaryPath,
356 ]);
357 final String lipoInfo = infoResult.stdout as String;
358
359 final ProcessResult verifyResult = await environment.processManager.run(<String>[
360 'lipo',
361 frameworkBinaryPath,
362 '-verify_arch',
363 ...archList,
364 ]);
365
366 if (verifyResult.exitCode != 0) {
367 throw Exception(
368 'Binary $frameworkBinaryPath does not contain architectures "$archs".\n'
369 '\n'
370 'lipo -info:\n'
371 '$lipoInfo',
372 );
373 }
374
375 // Skip thinning for non-fat executables.
376 if (lipoInfo.startsWith('Non-fat file:')) {
377 environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
378 return;
379 }
380
381 // Thin in-place.
382 final ProcessResult extractResult = await environment.processManager.run(<String>[
383 'lipo',
384 '-output',
385 frameworkBinaryPath,
386 for (final String arch in archList) ...<String>['-extract', arch],
387 ...<String>[frameworkBinaryPath],
388 ]);
389
390 if (extractResult.exitCode != 0) {
391 throw Exception(
392 'Failed to extract architectures "$archs" for $frameworkBinaryPath.\n'
393 '\n'
394 'stderr:\n'
395 '${extractResult.stderr}\n\n'
396 'lipo -info:\n'
397 '$lipoInfo',
398 );
399 }
400 }
401}
402
403/// Unpack the release prebuilt engine framework.
404class ReleaseUnpackIOS extends UnpackIOS {
405 const ReleaseUnpackIOS();
406
407 @override
408 String get name => 'release_unpack_ios';
409
410 @override
411 BuildMode get buildMode => BuildMode.release;
412}
413
414/// Unpack the profile prebuilt engine framework.
415class ProfileUnpackIOS extends UnpackIOS {
416 const ProfileUnpackIOS();
417
418 @override
419 String get name => 'profile_unpack_ios';
420
421 @override
422 BuildMode get buildMode => BuildMode.profile;
423}
424
425/// Unpack the debug prebuilt engine framework.
426class DebugUnpackIOS extends UnpackIOS {
427 const DebugUnpackIOS();
428
429 @override
430 String get name => 'debug_unpack_ios';
431
432 @override
433 BuildMode get buildMode => BuildMode.debug;
434}
435
436abstract class IosLLDBInit extends Target {
437 const IosLLDBInit();
438
439 @override
440 List<Source> get inputs => <Source>[
441 const Source.pattern(
442 '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart',
443 ),
444 ];
445
446 @override
447 List<Source> get outputs => <Source>[
448 Source.fromProject((FlutterProject project) => project.ios.lldbInitFile),
449 ];
450
451 @override
452 List<Target> get dependencies => <Target>[];
453
454 @visibleForOverriding
455 BuildMode get buildMode;
456
457 @override
458 Future<void> build(Environment environment) async {
459 final String? sdkRoot = environment.defines[kSdkRoot];
460 if (sdkRoot == null) {
461 throw MissingDefineException(kSdkRoot, name);
462 }
463 final EnvironmentType? environmentType = environmentTypeFromSdkroot(
464 sdkRoot,
465 environment.fileSystem,
466 );
467
468 // LLDB Init File is only required for physical devices in debug mode.
469 if (!buildMode.isJit || environmentType != EnvironmentType.physical) {
470 return;
471 }
472
473 final String? targetDeviceVersionString = environment.defines[kTargetDeviceOSVersion];
474 if (targetDeviceVersionString == null) {
475 // Skip if TARGET_DEVICE_OS_VERSION is not found. TARGET_DEVICE_OS_VERSION
476 // is not set if "build ios-framework" is called, which builds the
477 // DebugIosApplicationBundle directly rather than through flutter assemble.
478 // If may also be null if the build is targeting multiple device types.
479 return;
480 }
481 final Version? targetDeviceVersion = Version.parse(targetDeviceVersionString);
482 if (targetDeviceVersion == null) {
483 environment.logger.printError(
484 'Failed to parse Target Device Version $targetDeviceVersionString',
485 );
486 return;
487 }
488
489 // LLDB Init File is only needed for iOS 18.4+.
490 if (targetDeviceVersion < Version(18, 4, null)) {
491 return;
492 }
493
494 // The scheme name is not available in Xcode Build Phases Run Scripts.
495 // Instead, find all xcscheme files in the Xcode project (this may be the
496 // Flutter Xcode project or an Add to App native Xcode project) and check
497 // if any of them contain "customLLDBInitFile". If none have it set, throw
498 // an error.
499 final String? srcRoot = environment.defines[kSrcRoot];
500 if (srcRoot == null) {
501 environment.logger.printError('Failed to find $srcRoot');
502 return;
503 }
504 final Directory xcodeProjectDir = environment.fileSystem.directory(srcRoot);
505 if (!xcodeProjectDir.existsSync()) {
506 environment.logger.printError('Failed to find ${xcodeProjectDir.path}');
507 return;
508 }
509
510 bool anyLLDBInitFound = false;
511 await for (final FileSystemEntity entity in xcodeProjectDir.list(recursive: true)) {
512 if (environment.fileSystem.path.extension(entity.path) == '.xcscheme' && entity is File) {
513 if (entity.readAsStringSync().contains('customLLDBInitFile')) {
514 anyLLDBInitFound = true;
515 break;
516 }
517 }
518 }
519 if (!anyLLDBInitFound) {
520 final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
521 if (flutterProject.isModule) {
522 // We use print here to make sure Xcode adds the message to the build logs. See
523 // https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build#Log-errors-and-warnings-from-your-script
524 // ignore: avoid_print
525 print(
526 'warning: Debugging Flutter on new iOS versions requires an LLDB Init File. To '
527 'ensure debug mode works, please run "flutter build ios --config-only" '
528 'in your Flutter project and follow the instructions to add the file.',
529 );
530 } else {
531 // We use print here to make sure Xcode adds the message to the build logs. See
532 // https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build#Log-errors-and-warnings-from-your-script
533 // ignore: avoid_print
534 print(
535 'warning: Debugging Flutter on new iOS versions requires an LLDB Init File. To '
536 'ensure debug mode works, please run "flutter build ios --config-only" '
537 'in your Flutter project and automatically add the files.',
538 );
539 }
540 }
541 return;
542 }
543}
544
545class DebugIosLLDBInit extends IosLLDBInit {
546 const DebugIosLLDBInit();
547
548 @override
549 String get name => 'debug_ios_lldb_init';
550
551 @override
552 BuildMode get buildMode => BuildMode.debug;
553}
554
555/// The base class for all iOS bundle targets.
556///
557/// This is responsible for setting up the basic App.framework structure, including:
558/// * Copying the app.dill/kernel_blob.bin from the build directory to assets (debug)
559/// * Copying the precompiled isolate/vm data from the engine (debug)
560/// * Copying the flutter assets to App.framework/flutter_assets
561/// * Copying either the stub or real App assembly file to App.framework/App
562abstract class IosAssetBundle extends Target {
563 const IosAssetBundle();
564
565 @override
566 List<Target> get dependencies => const <Target>[KernelSnapshot(), InstallCodeAssets()];
567
568 @override
569 List<Source> get inputs => const <Source>[
570 Source.pattern('{BUILD_DIR}/App.framework/App'),
571 Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
572 ...IconTreeShaker.inputs,
573 ...ShaderCompiler.inputs,
574 ];
575
576 @override
577 List<Source> get outputs => const <Source>[
578 Source.pattern('{OUTPUT_DIR}/App.framework/App'),
579 Source.pattern('{OUTPUT_DIR}/App.framework/Info.plist'),
580 ];
581
582 @override
583 List<String> get depfiles => <String>['flutter_assets.d'];
584
585 @override
586 Future<void> build(Environment environment) async {
587 final String? environmentBuildMode = environment.defines[kBuildMode];
588 if (environmentBuildMode == null) {
589 throw MissingDefineException(kBuildMode, name);
590 }
591 final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode);
592 final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
593 final File frameworkBinary = frameworkDirectory.childFile('App');
594 final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
595 frameworkDirectory.createSync(recursive: true);
596 assetDirectory.createSync();
597
598 // Only copy the prebuilt runtimes and kernel blob in debug mode.
599 if (buildMode == BuildMode.debug) {
600 // Copy the App.framework to the output directory.
601 environment.buildDir
602 .childDirectory('App.framework')
603 .childFile('App')
604 .copySync(frameworkBinary.path);
605
606 final String vmSnapshotData = environment.artifacts.getArtifactPath(
607 Artifact.vmSnapshotData,
608 mode: BuildMode.debug,
609 );
610 final String isolateSnapshotData = environment.artifacts.getArtifactPath(
611 Artifact.isolateSnapshotData,
612 mode: BuildMode.debug,
613 );
614 environment.buildDir
615 .childFile('app.dill')
616 .copySync(assetDirectory.childFile('kernel_blob.bin').path);
617 environment.fileSystem
618 .file(vmSnapshotData)
619 .copySync(assetDirectory.childFile('vm_snapshot_data').path);
620 environment.fileSystem
621 .file(isolateSnapshotData)
622 .copySync(assetDirectory.childFile('isolate_snapshot_data').path);
623 } else {
624 environment.buildDir
625 .childDirectory('App.framework')
626 .childFile('App')
627 .copySync(frameworkBinary.path);
628 }
629
630 // Copy the dSYM
631 if (environment.buildDir.childDirectory('App.framework.dSYM').existsSync()) {
632 final File dsymOutputBinary = environment.outputDir
633 .childDirectory('App.framework.dSYM')
634 .childDirectory('Contents')
635 .childDirectory('Resources')
636 .childDirectory('DWARF')
637 .childFile('App');
638 dsymOutputBinary.parent.createSync(recursive: true);
639 environment.buildDir
640 .childDirectory('App.framework.dSYM')
641 .childDirectory('Contents')
642 .childDirectory('Resources')
643 .childDirectory('DWARF')
644 .childFile('App')
645 .copySync(dsymOutputBinary.path);
646 }
647
648 final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
649 final String? flavor = await flutterProject.ios.parseFlavorFromConfiguration(environment);
650
651 // Copy the assets.
652 final Depfile assetDepfile = await copyAssets(
653 environment,
654 assetDirectory,
655 targetPlatform: TargetPlatform.ios,
656 buildMode: buildMode,
657 additionalInputs: <File>[
658 flutterProject.ios.infoPlist,
659 flutterProject.ios.appFrameworkInfoPlist,
660 ],
661 additionalContent: <String, DevFSContent>{
662 'NativeAssetsManifest.json': DevFSFileContent(
663 environment.buildDir.childFile('native_assets.json'),
664 ),
665 },
666 flavor: flavor,
667 );
668 environment.depFileService.writeToFile(
669 assetDepfile,
670 environment.buildDir.childFile('flutter_assets.d'),
671 );
672
673 // Copy the plist from either the project or module.
674 flutterProject.ios.appFrameworkInfoPlist.copySync(
675 environment.outputDir.childDirectory('App.framework').childFile('Info.plist').path,
676 );
677
678 await _signFramework(environment, frameworkBinary, buildMode);
679 }
680}
681
682/// Build a debug iOS application bundle.
683class DebugIosApplicationBundle extends IosAssetBundle {
684 const DebugIosApplicationBundle();
685
686 @override
687 String get name => 'debug_ios_bundle_flutter_assets';
688
689 @override
690 List<Source> get inputs => <Source>[
691 const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
692 const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
693 const Source.pattern('{BUILD_DIR}/app.dill'),
694 ...super.inputs,
695 ];
696
697 @override
698 List<Source> get outputs => <Source>[
699 const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/vm_snapshot_data'),
700 const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/isolate_snapshot_data'),
701 const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/kernel_blob.bin'),
702 ...super.outputs,
703 ];
704
705 @override
706 List<Target> get dependencies => <Target>[
707 const DebugUniversalFramework(),
708 const DebugIosLLDBInit(),
709 ...super.dependencies,
710 ];
711}
712
713/// IosAssetBundle with debug symbols, used for Profile and Release builds.
714abstract class _IosAssetBundleWithDSYM extends IosAssetBundle {
715 const _IosAssetBundleWithDSYM();
716
717 @override
718 List<Source> get inputs => <Source>[
719 ...super.inputs,
720 const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
721 ];
722
723 @override
724 List<Source> get outputs => <Source>[
725 ...super.outputs,
726 const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
727 ];
728}
729
730/// Build a profile iOS application bundle.
731class ProfileIosApplicationBundle extends _IosAssetBundleWithDSYM {
732 const ProfileIosApplicationBundle();
733
734 @override
735 String get name => 'profile_ios_bundle_flutter_assets';
736
737 @override
738 List<Target> get dependencies => const <Target>[AotAssemblyProfile(), InstallCodeAssets()];
739}
740
741/// Build a release iOS application bundle.
742class ReleaseIosApplicationBundle extends _IosAssetBundleWithDSYM {
743 const ReleaseIosApplicationBundle();
744
745 @override
746 String get name => 'release_ios_bundle_flutter_assets';
747
748 @override
749 List<Target> get dependencies => const <Target>[AotAssemblyRelease(), InstallCodeAssets()];
750
751 @override
752 Future<void> build(Environment environment) async {
753 bool buildSuccess = true;
754 try {
755 await super.build(environment);
756 } catch (_) {
757 buildSuccess = false;
758 rethrow;
759 } finally {
760 // Send a usage event when the app is being archived.
761 // Since assemble is run during a `flutter build`/`run` as well as an out-of-band
762 // archive command from Xcode, this is a more accurate count than `flutter build ipa` alone.
763 if (environment.defines[kXcodeAction]?.toLowerCase() == 'install') {
764 environment.logger.printTrace('Sending archive event if usage enabled.');
765 environment.analytics.send(
766 Event.appleUsageEvent(
767 workflow: 'assemble',
768 parameter: 'ios-archive',
769 result: buildSuccess ? 'success' : 'fail',
770 ),
771 );
772 }
773 }
774 }
775}
776
777/// Create an App.framework for debug iOS targets.
778///
779/// This framework needs to exist for the Xcode project to link/bundle,
780/// but it isn't actually executed. To generate something valid, we compile a trivial
781/// constant.
782Future<void> _createStubAppFramework(
783 File outputFile,
784 Environment environment,
785 Set<String>? iosArchNames,
786 String sdkRoot,
787) async {
788 try {
789 outputFile.createSync(recursive: true);
790 } on Exception catch (e) {
791 throwToolExit('Failed to create App.framework stub at ${outputFile.path}: $e');
792 }
793
794 final FileSystem fileSystem = environment.fileSystem;
795 final Directory tempDir = fileSystem.systemTempDirectory.createTempSync(
796 'flutter_tools_stub_source.',
797 );
798 try {
799 final File stubSource = tempDir.childFile('debug_app.cc')..writeAsStringSync(r'''
800 static const int Moo = 88;
801 ''');
802
803 final EnvironmentType? environmentType = environmentTypeFromSdkroot(sdkRoot, fileSystem);
804
805 await globals.xcode!.clang(<String>[
806 '-x',
807 'c',
808 for (final String arch in iosArchNames ?? <String>{}) ...<String>['-arch', arch],
809 stubSource.path,
810 '-dynamiclib',
811 // Keep version in sync with AOTSnapshotter flag
812 if (environmentType == EnvironmentType.physical)
813 '-miphoneos-version-min=12.0'
814 else
815 '-miphonesimulator-version-min=12.0',
816 '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
817 '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
818 '-fapplication-extension',
819 '-install_name', '@rpath/App.framework/App',
820 '-isysroot', sdkRoot,
821 '-o', outputFile.path,
822 ]);
823 } finally {
824 try {
825 tempDir.deleteSync(recursive: true);
826 } on FileSystemException {
827 // Best effort. Sometimes we can't delete things from system temp.
828 } on Exception catch (e) {
829 throwToolExit('Failed to create App.framework stub at ${outputFile.path}: $e');
830 }
831 }
832
833 await _signFramework(environment, outputFile, BuildMode.debug);
834}
835
836Future<void> _signFramework(Environment environment, File binary, BuildMode buildMode) async {
837 await removeFinderExtendedAttributes(
838 binary,
839 ProcessUtils(processManager: environment.processManager, logger: environment.logger),
840 environment.logger,
841 );
842
843 String? codesignIdentity = environment.defines[kCodesignIdentity];
844 if (codesignIdentity == null || codesignIdentity.isEmpty) {
845 codesignIdentity = '-';
846 }
847 final ProcessResult result = environment.processManager.runSync(<String>[
848 'codesign',
849 '--force',
850 '--sign',
851 codesignIdentity,
852 if (buildMode != BuildMode.release) ...<String>[
853 // Mimic Xcode's timestamp codesigning behavior on non-release binaries.
854 '--timestamp=none',
855 ],
856 binary.path,
857 ]);
858 if (result.exitCode != 0) {
859 final String stdout = (result.stdout as String).trim();
860 final String stderr = (result.stderr as String).trim();
861 final StringBuffer output = StringBuffer();
862 output.writeln('Failed to codesign ${binary.path} with identity $codesignIdentity.');
863 if (stdout.isNotEmpty) {
864 output.writeln(stdout);
865 }
866 if (stderr.isNotEmpty) {
867 output.writeln(stderr);
868 }
869 throw Exception(output.toString());
870 }
871}
872

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com