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:unified_analytics/unified_analytics.dart';
6
7import '../../artifacts.dart';
8import '../../base/build.dart';
9import '../../base/file_system.dart';
10import '../../base/io.dart';
11import '../../base/process.dart';
12import '../../build_info.dart';
13import '../../globals.dart' as globals show xcode;
14import '../../reporting/reporting.dart';
15import '../build_system.dart';
16import '../depfile.dart';
17import '../exceptions.dart';
18import 'assets.dart';
19import 'common.dart';
20import 'icon_tree_shaker.dart';
21
22/// Copy the macOS framework to the correct copy dir by invoking 'rsync'.
23///
24/// This class is abstract to share logic between the three concrete
25/// implementations. The shelling out is done to avoid complications with
26/// preserving special files (e.g., symbolic links) in the framework structure.
27///
28/// The real implementations are:
29/// * [DebugUnpackMacOS]
30/// * [ProfileUnpackMacOS]
31/// * [ReleaseUnpackMacOS]
32abstract class UnpackMacOS extends Target {
33 const UnpackMacOS();
34
35 @override
36 List<Source> get inputs => const <Source>[
37 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
38 ];
39
40 @override
41 List<Source> get outputs => const <Source>[
42 Source.pattern('{OUTPUT_DIR}/FlutterMacOS.framework/Versions/A/FlutterMacOS'),
43 ];
44
45 @override
46 List<Target> get dependencies => <Target>[];
47
48 @override
49 Future<void> build(Environment environment) async {
50 final String? buildModeEnvironment = environment.defines[kBuildMode];
51 if (buildModeEnvironment == null) {
52 throw MissingDefineException(kBuildMode, 'unpack_macos');
53 }
54
55 // Copy Flutter framework.
56 final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
57 final String basePath = environment.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);
58 final ProcessResult result = environment.processManager.runSync(<String>[
59 'rsync',
60 '-av',
61 '--delete',
62 '--filter',
63 '- .DS_Store/',
64 '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
65 basePath,
66 environment.outputDir.path,
67 ]);
68
69 _removeDenylistedFiles(environment.outputDir);
70 if (result.exitCode != 0) {
71 throw Exception(
72 'Failed to copy framework (exit ${result.exitCode}:\n'
73 '${result.stdout}\n---\n${result.stderr}',
74 );
75 }
76
77 final File frameworkBinary = environment.outputDir
78 .childDirectory('FlutterMacOS.framework')
79 .childDirectory('Versions')
80 .childDirectory('A')
81 .childFile('FlutterMacOS');
82 final String frameworkBinaryPath = frameworkBinary.path;
83 if (!frameworkBinary.existsSync()) {
84 throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
85 }
86
87 await _thinFramework(environment, frameworkBinaryPath);
88 }
89
90 /// Files that should not be copied to build output directory if found during framework copy step.
91 static const List<String> _copyDenylist = <String>[
92 'entitlements.txt',
93 'without_entitlements.txt',
94 'unsigned_binaries.txt',
95 ];
96
97 void _removeDenylistedFiles(Directory directory) {
98 for (final FileSystemEntity entity in directory.listSync(recursive: true)) {
99 if (entity is! File) {
100 continue;
101 }
102 if (_copyDenylist.contains(entity.basename)) {
103 entity.deleteSync();
104 }
105 }
106 }
107
108 Future<void> _thinFramework(
109 Environment environment,
110 String frameworkBinaryPath,
111 ) async {
112 final String archs = environment.defines[kDarwinArchs] ?? 'x86_64 arm64';
113 final List<String> archList = archs.split(' ').toList();
114 final ProcessResult infoResult =
115 await environment.processManager.run(<String>[
116 'lipo',
117 '-info',
118 frameworkBinaryPath,
119 ]);
120 final String lipoInfo = infoResult.stdout as String;
121
122 final ProcessResult verifyResult = await environment.processManager.run(<String>[
123 'lipo',
124 frameworkBinaryPath,
125 '-verify_arch',
126 ...archList,
127 ]);
128
129 if (verifyResult.exitCode != 0) {
130 throw Exception('Binary $frameworkBinaryPath does not contain $archs. Running lipo -info:\n$lipoInfo');
131 }
132
133 // Skip thinning for non-fat executables.
134 if (lipoInfo.startsWith('Non-fat file:')) {
135 environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
136 return;
137 }
138
139 // Thin in-place.
140 final ProcessResult extractResult = environment.processManager.runSync(<String>[
141 'lipo',
142 '-output',
143 frameworkBinaryPath,
144 for (final String arch in archList)
145 ...<String>[
146 '-extract',
147 arch,
148 ],
149 ...<String>[frameworkBinaryPath],
150 ]);
151
152 if (extractResult.exitCode != 0) {
153 throw Exception('Failed to extract $archs for $frameworkBinaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo');
154 }
155 }
156}
157
158/// Unpack the release prebuilt engine framework.
159class ReleaseUnpackMacOS extends UnpackMacOS {
160 const ReleaseUnpackMacOS();
161
162 @override
163 String get name => 'release_unpack_macos';
164
165 @override
166 List<Source> get outputs => super.outputs + const <Source>[
167 Source.pattern('{OUTPUT_DIR}/FlutterMacOS.framework.dSYM/Contents/Resources/DWARF/FlutterMacOS'),
168 ];
169
170 @override
171 List<Source> get inputs => super.inputs + const <Source>[
172 Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.release),
173 ];
174
175 @override
176 Future<void> build(Environment environment) async {
177 await super.build(environment);
178
179 // Copy Flutter framework dSYM (debug symbol) bundle, if present.
180 final String? buildModeEnvironment = environment.defines[kBuildMode];
181 if (buildModeEnvironment == null) {
182 throw MissingDefineException(kBuildMode, 'unpack_macos');
183 }
184 final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
185 final Directory frameworkDsym = environment.fileSystem.directory(
186 environment.artifacts.getArtifactPath(
187 Artifact.flutterMacOSFrameworkDsym,
188 platform: TargetPlatform.darwin,
189 mode: buildMode,
190 )
191 );
192 if (frameworkDsym.existsSync()) {
193 final ProcessResult result = await environment.processManager.run(<String>[
194 'rsync',
195 '-av',
196 '--delete',
197 '--filter',
198 '- .DS_Store/',
199 '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
200 frameworkDsym.path,
201 environment.outputDir.path,
202 ]);
203 if (result.exitCode != 0) {
204 throw Exception(
205 'Failed to copy framework dSYM (exit ${result.exitCode}:\n'
206 '${result.stdout}\n---\n${result.stderr}',
207 );
208 }
209 }
210 }
211}
212
213/// Unpack the profile prebuilt engine framework.
214class ProfileUnpackMacOS extends UnpackMacOS {
215 const ProfileUnpackMacOS();
216
217 @override
218 String get name => 'profile_unpack_macos';
219
220 @override
221 List<Source> get inputs => <Source>[
222 ...super.inputs,
223 const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.profile),
224 ];
225}
226
227/// Unpack the debug prebuilt engine framework.
228class DebugUnpackMacOS extends UnpackMacOS {
229 const DebugUnpackMacOS();
230
231 @override
232 String get name => 'debug_unpack_macos';
233
234 @override
235 List<Source> get inputs => <Source>[
236 ...super.inputs,
237 const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.debug),
238 ];
239}
240
241/// Create an App.framework for debug macOS targets.
242///
243/// This framework needs to exist for the Xcode project to link/bundle,
244/// but it isn't actually executed. To generate something valid, we compile a trivial
245/// constant.
246class DebugMacOSFramework extends Target {
247 const DebugMacOSFramework();
248
249 @override
250 String get name => 'debug_macos_framework';
251
252 @override
253 Future<void> build(Environment environment) async {
254 final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join(
255 environment.buildDir.path, 'App.framework', 'App'));
256
257 final Iterable<DarwinArch> darwinArchs = environment.defines[kDarwinArchs]
258 ?.split(' ')
259 .map(getDarwinArchForName)
260 ?? <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64];
261
262 final Iterable<String> darwinArchArguments =
263 darwinArchs.expand((DarwinArch arch) => <String>['-arch', arch.name]);
264
265 outputFile.createSync(recursive: true);
266 final File debugApp = environment.buildDir.childFile('debug_app.cc')
267 ..writeAsStringSync(r'''
268static const int Moo = 88;
269''');
270 final RunResult result = await globals.xcode!.clang(<String>[
271 '-x',
272 'c',
273 debugApp.path,
274 ...darwinArchArguments,
275 '-dynamiclib',
276 '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
277 '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
278 '-fapplication-extension',
279 '-install_name', '@rpath/App.framework/App',
280 '-o', outputFile.path,
281 ]);
282 if (result.exitCode != 0) {
283 throw Exception('Failed to compile debug App.framework');
284 }
285 }
286
287 @override
288 List<Target> get dependencies => const <Target>[];
289
290 @override
291 List<Source> get inputs => const <Source>[
292 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
293 ];
294
295 @override
296 List<Source> get outputs => const <Source>[
297 Source.pattern('{BUILD_DIR}/App.framework/App'),
298 ];
299}
300
301class CompileMacOSFramework extends Target {
302 const CompileMacOSFramework();
303
304 @override
305 String get name => 'compile_macos_framework';
306
307 @override
308 Future<void> build(Environment environment) async {
309 final String? buildModeEnvironment = environment.defines[kBuildMode];
310 if (buildModeEnvironment == null) {
311 throw MissingDefineException(kBuildMode, 'compile_macos_framework');
312 }
313 final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
314 if (targetPlatformEnvironment == null) {
315 throw MissingDefineException(kTargetPlatform, 'kernel_snapshot');
316 }
317 final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
318 if (buildMode == BuildMode.debug) {
319 throw Exception('precompiled macOS framework only supported in release/profile builds.');
320 }
321 final String buildOutputPath = environment.buildDir.path;
322 final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory];
323 final String? splitDebugInfo = environment.defines[kSplitDebugInfo];
324 final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
325 final List<String> extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions);
326 final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
327 final List<DarwinArch> darwinArchs = environment.defines[kDarwinArchs]
328 ?.split(' ')
329 .map(getDarwinArchForName)
330 .toList()
331 ?? <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64];
332 if (targetPlatform != TargetPlatform.darwin) {
333 throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.');
334 }
335
336 final AOTSnapshotter snapshotter = AOTSnapshotter(
337 fileSystem: environment.fileSystem,
338 logger: environment.logger,
339 xcode: globals.xcode!,
340 artifacts: environment.artifacts,
341 processManager: environment.processManager
342 );
343
344 final List<Future<int>> pending = <Future<int>>[];
345 for (final DarwinArch darwinArch in darwinArchs) {
346 if (codeSizeDirectory != null) {
347 final File codeSizeFile = environment.fileSystem
348 .directory(codeSizeDirectory)
349 .childFile('snapshot.${darwinArch.name}.json');
350 final File precompilerTraceFile = environment.fileSystem
351 .directory(codeSizeDirectory)
352 .childFile('trace.${darwinArch.name}.json');
353 extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
354 extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
355 }
356
357 pending.add(snapshotter.build(
358 buildMode: buildMode,
359 mainPath: environment.buildDir.childFile('app.dill').path,
360 outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name),
361 platform: TargetPlatform.darwin,
362 darwinArch: darwinArch,
363 splitDebugInfo: splitDebugInfo,
364 dartObfuscation: dartObfuscation,
365 extraGenSnapshotOptions: extraGenSnapshotOptions,
366 ));
367 }
368
369 final List<int> results = await Future.wait(pending);
370 if (results.any((int result) => result != 0)) {
371 throw Exception('AOT snapshotter exited with code ${results.join()}');
372 }
373
374 // Combine the app lib into a fat framework.
375 await Lipo.create(
376 environment,
377 darwinArchs,
378 relativePath: 'App.framework/App',
379 inputDir: buildOutputPath,
380 );
381
382 // And combine the dSYM for each architecture too, if it was created.
383 await Lipo.create(
384 environment,
385 darwinArchs,
386 relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App',
387 inputDir: buildOutputPath,
388 // Don't fail if the dSYM wasn't created (i.e. during a debug build).
389 skipMissingInputs: true,
390 );
391 }
392
393 @override
394 List<Target> get dependencies => const <Target>[
395 KernelSnapshot(),
396 ];
397
398 @override
399 List<Source> get inputs => const <Source>[
400 Source.pattern('{BUILD_DIR}/app.dill'),
401 Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
402 Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin),
403 ];
404
405 @override
406 List<Source> get outputs => const <Source>[
407 Source.pattern('{BUILD_DIR}/App.framework/App'),
408 Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
409 ];
410}
411
412/// Bundle the flutter assets into the App.framework.
413///
414/// In debug mode, also include the app.dill and precompiled runtimes.
415///
416/// See https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
417/// for more information on Framework structure.
418abstract class MacOSBundleFlutterAssets extends Target {
419 const MacOSBundleFlutterAssets();
420
421 @override
422 List<Source> get inputs => const <Source>[
423 Source.pattern('{BUILD_DIR}/App.framework/App'),
424 ...IconTreeShaker.inputs,
425 ];
426
427 @override
428 List<Source> get outputs => const <Source>[
429 Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/App'),
430 Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/Info.plist'),
431 ];
432
433 @override
434 List<String> get depfiles => const <String>[
435 'flutter_assets.d',
436 ];
437
438 @override
439 Future<void> build(Environment environment) async {
440 final String? buildModeEnvironment = environment.defines[kBuildMode];
441 if (buildModeEnvironment == null) {
442 throw MissingDefineException(kBuildMode, 'compile_macos_framework');
443 }
444
445 final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
446 final Directory frameworkRootDirectory = environment
447 .outputDir
448 .childDirectory('App.framework');
449 final Directory outputDirectory = frameworkRootDirectory
450 .childDirectory('Versions')
451 .childDirectory('A')
452 ..createSync(recursive: true);
453
454 // Copy App into framework directory.
455 environment.buildDir
456 .childDirectory('App.framework')
457 .childFile('App')
458 .copySync(outputDirectory.childFile('App').path);
459
460 // Copy the dSYM
461 if (environment.buildDir.childDirectory('App.framework.dSYM').existsSync()) {
462 final File dsymOutputBinary = environment
463 .outputDir
464 .childDirectory('App.framework.dSYM')
465 .childDirectory('Contents')
466 .childDirectory('Resources')
467 .childDirectory('DWARF')
468 .childFile('App');
469 dsymOutputBinary.parent.createSync(recursive: true);
470 environment
471 .buildDir
472 .childDirectory('App.framework.dSYM')
473 .childDirectory('Contents')
474 .childDirectory('Resources')
475 .childDirectory('DWARF')
476 .childFile('App')
477 .copySync(dsymOutputBinary.path);
478 }
479
480 // Copy assets into asset directory.
481 final Directory assetDirectory = outputDirectory
482 .childDirectory('Resources')
483 .childDirectory('flutter_assets');
484 assetDirectory.createSync(recursive: true);
485
486 final Depfile assetDepfile = await copyAssets(
487 environment,
488 assetDirectory,
489 targetPlatform: TargetPlatform.darwin,
490 buildMode: buildMode,
491 flavor: environment.defines[kFlavor],
492 );
493 environment.depFileService.writeToFile(
494 assetDepfile,
495 environment.buildDir.childFile('flutter_assets.d'),
496 );
497
498 // Copy Info.plist template.
499 assetDirectory.parent.childFile('Info.plist')
500 ..createSync()
501 ..writeAsStringSync(r'''
502<?xml version="1.0" encoding="UTF-8"?>
503<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
504<plist version="1.0">
505<dict>
506 <key>CFBundleDevelopmentRegion</key>
507 <string>en</string>
508 <key>CFBundleExecutable</key>
509 <string>App</string>
510 <key>CFBundleIdentifier</key>
511 <string>io.flutter.flutter.app</string>
512 <key>CFBundleInfoDictionaryVersion</key>
513 <string>6.0</string>
514 <key>CFBundleName</key>
515 <string>App</string>
516 <key>CFBundlePackageType</key>
517 <string>FMWK</string>
518 <key>CFBundleShortVersionString</key>
519 <string>1.0</string>
520 <key>CFBundleVersion</key>
521 <string>1.0</string>
522</dict>
523</plist>
524
525''');
526 if (buildMode == BuildMode.debug) {
527 // Copy dill file.
528 try {
529 final File sourceFile = environment.buildDir.childFile('app.dill');
530 sourceFile.copySync(assetDirectory.childFile('kernel_blob.bin').path);
531 } on Exception catch (err) {
532 throw Exception('Failed to copy app.dill: $err');
533 }
534 // Copy precompiled runtimes.
535 try {
536 final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData,
537 platform: TargetPlatform.darwin, mode: BuildMode.debug);
538 final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData,
539 platform: TargetPlatform.darwin, mode: BuildMode.debug);
540 environment.fileSystem.file(vmSnapshotData).copySync(
541 assetDirectory.childFile('vm_snapshot_data').path);
542 environment.fileSystem.file(isolateSnapshotData).copySync(
543 assetDirectory.childFile('isolate_snapshot_data').path);
544 } on Exception catch (err) {
545 throw Exception('Failed to copy precompiled runtimes: $err');
546 }
547 }
548 // Create symlink to current version. These must be relative, from the
549 // framework root for Resources/App and from the versions root for
550 // Current.
551 try {
552 final Link currentVersion = outputDirectory.parent
553 .childLink('Current');
554 if (!currentVersion.existsSync()) {
555 final String linkPath = environment.fileSystem.path.relative(outputDirectory.path,
556 from: outputDirectory.parent.path);
557 currentVersion.createSync(linkPath);
558 }
559 // Create symlink to current resources.
560 final Link currentResources = frameworkRootDirectory
561 .childLink('Resources');
562 if (!currentResources.existsSync()) {
563 final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'Resources'),
564 from: frameworkRootDirectory.path);
565 currentResources.createSync(linkPath);
566 }
567 // Create symlink to current binary.
568 final Link currentFramework = frameworkRootDirectory
569 .childLink('App');
570 if (!currentFramework.existsSync()) {
571 final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'App'),
572 from: frameworkRootDirectory.path);
573 currentFramework.createSync(linkPath);
574 }
575 } on FileSystemException {
576 throw Exception('Failed to create symlinks for framework. try removing '
577 'the "${environment.outputDir.path}" directory and rerunning');
578 }
579 }
580}
581
582/// Bundle the debug flutter assets into the App.framework.
583class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
584 const DebugMacOSBundleFlutterAssets();
585
586 @override
587 String get name => 'debug_macos_bundle_flutter_assets';
588
589 @override
590 List<Target> get dependencies => const <Target>[
591 KernelSnapshot(),
592 DebugMacOSFramework(),
593 DebugUnpackMacOS(),
594 ];
595
596 @override
597 List<Source> get inputs => <Source>[
598 ...super.inputs,
599 const Source.pattern('{BUILD_DIR}/app.dill'),
600 const Source.artifact(Artifact.isolateSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug),
601 const Source.artifact(Artifact.vmSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug),
602 ];
603
604 @override
605 List<Source> get outputs => <Source>[
606 ...super.outputs,
607 const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'),
608 const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
609 const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
610 ];
611}
612
613/// Bundle the profile flutter assets into the App.framework.
614class ProfileMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
615 const ProfileMacOSBundleFlutterAssets();
616
617 @override
618 String get name => 'profile_macos_bundle_flutter_assets';
619
620 @override
621 List<Target> get dependencies => const <Target>[
622 CompileMacOSFramework(),
623 ProfileUnpackMacOS(),
624 ];
625
626 @override
627 List<Source> get inputs => <Source>[
628 ...super.inputs,
629 const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
630 ];
631
632 @override
633 List<Source> get outputs => <Source>[
634 ...super.outputs,
635 const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
636 ];
637}
638
639
640/// Bundle the release flutter assets into the App.framework.
641class ReleaseMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
642 const ReleaseMacOSBundleFlutterAssets();
643
644 @override
645 String get name => 'release_macos_bundle_flutter_assets';
646
647 @override
648 List<Target> get dependencies => const <Target>[
649 CompileMacOSFramework(),
650 ReleaseUnpackMacOS(),
651 ];
652
653 @override
654 List<Source> get inputs => <Source>[
655 ...super.inputs,
656 const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
657 ];
658
659 @override
660 List<Source> get outputs => <Source>[
661 ...super.outputs,
662 const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
663 ];
664
665 @override
666 Future<void> build(Environment environment) async {
667 bool buildSuccess = true;
668 try {
669 await super.build(environment);
670 } catch (_) { // ignore: avoid_catches_without_on_clauses
671 buildSuccess = false;
672 rethrow;
673 } finally {
674 // Send a usage event when the app is being archived from Xcode.
675 if (environment.defines[kXcodeAction]?.toLowerCase() == 'install') {
676 environment.logger.printTrace('Sending archive event if usage enabled.');
677 UsageEvent(
678 'assemble',
679 'macos-archive',
680 label: buildSuccess ? 'success' : 'fail',
681 flutterUsage: environment.usage,
682 ).send();
683 environment.analytics.send(Event.appleUsageEvent(
684 workflow: 'assemble',
685 parameter: 'macos-archive',
686 result: buildSuccess ? 'success' : 'fail',
687 ));
688 }
689 }
690 }
691
692}
693

Provided by KDAB

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