macos.dart 21.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:unified_analytics/unified_analytics.dart';

7
import '../../artifacts.dart';
8
import '../../base/build.dart';
9 10
import '../../base/file_system.dart';
import '../../base/io.dart';
11
import '../../base/process.dart';
12
import '../../build_info.dart';
13
import '../../globals.dart' as globals show xcode;
14
import '../../reporting/reporting.dart';
15
import '../build_system.dart';
16
import '../depfile.dart';
17
import '../exceptions.dart';
18
import 'assets.dart';
19
import 'common.dart';
20
import '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 26
/// implementations. The shelling out is done to avoid complications with
/// preserving special files (e.g., symbolic links) in the framework structure.
27
///
28 29 30 31 32
/// The real implementations are:
///   * [DebugUnpackMacOS]
///   * [ProfileUnpackMacOS]
///   * [ReleaseUnpackMacOS]
abstract class UnpackMacOS extends Target {
33
  const UnpackMacOS();
34

35 36 37 38 39 40
  @override
  List<Source> get inputs => const <Source>[
    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
  ];

  @override
41
  List<Source> get outputs => const <Source>[
42
    Source.pattern('{OUTPUT_DIR}/FlutterMacOS.framework/Versions/A/FlutterMacOS'),
43
  ];
44 45 46

  @override
  List<Target> get dependencies => <Target>[];
47

48
  @override
49
  Future<void> build(Environment environment) async {
50 51
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
52 53
      throw MissingDefineException(kBuildMode, 'unpack_macos');
    }
54
    final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
55
    final String basePath = environment.artifacts.getArtifactPath(Artifact.flutterMacOSFramework, mode: buildMode);
56 57 58 59 60 61 62 63 64 65

    final ProcessResult result = environment.processManager.runSync(<String>[
      'rsync',
      '-av',
      '--delete',
      '--filter',
      '- .DS_Store/',
      basePath,
      environment.outputDir.path,
    ]);
66 67

    _removeDenylistedFiles(environment.outputDir);
68 69 70
    if (result.exitCode != 0) {
      throw Exception(
        'Failed to copy framework (exit ${result.exitCode}:\n'
71
            '${result.stdout}\n---\n${result.stderr}',
72 73
      );
    }
74

75 76 77 78 79
    final File frameworkBinary = environment.outputDir
      .childDirectory('FlutterMacOS.framework')
      .childDirectory('Versions')
      .childDirectory('A')
      .childFile('FlutterMacOS');
80 81 82 83
    final String frameworkBinaryPath = frameworkBinary.path;
    if (!frameworkBinary.existsSync()) {
      throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
    }
84
    await _thinFramework(environment, frameworkBinaryPath);
85 86
  }

87 88 89 90 91 92 93 94 95 96 97 98 99
  static const List<String> _copyDenylist = <String>['entitlements.txt', 'without_entitlements.txt'];

  void _removeDenylistedFiles(Directory directory) {
    for (final FileSystemEntity entity in directory.listSync(recursive: true)) {
      if (entity is! File) {
        continue;
      }
      if (_copyDenylist.contains(entity.basename)) {
        entity.deleteSync();
      }
    }
  }

100 101 102 103
  Future<void> _thinFramework(
    Environment environment,
    String frameworkBinaryPath,
  ) async {
104
    final String archs = environment.defines[kDarwinArchs] ?? 'x86_64 arm64';
105
    final List<String> archList = archs.split(' ').toList();
106 107
    final ProcessResult infoResult =
        await environment.processManager.run(<String>[
108 109 110 111 112 113
      'lipo',
      '-info',
      frameworkBinaryPath,
    ]);
    final String lipoInfo = infoResult.stdout as String;

114
    final ProcessResult verifyResult = await environment.processManager.run(<String>[
115 116 117
      'lipo',
      frameworkBinaryPath,
      '-verify_arch',
118
      ...archList,
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
    ]);

    if (verifyResult.exitCode != 0) {
      throw Exception('Binary $frameworkBinaryPath does not contain $archs. Running lipo -info:\n$lipoInfo');
    }

    // Skip thinning for non-fat executables.
    if (lipoInfo.startsWith('Non-fat file:')) {
      environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
      return;
    }

    // Thin in-place.
    final ProcessResult extractResult = environment.processManager.runSync(<String>[
      'lipo',
      '-output',
      frameworkBinaryPath,
      for (final String arch in archList)
        ...<String>[
          '-extract',
          arch,
        ],
      ...<String>[frameworkBinaryPath],
    ]);

    if (extractResult.exitCode != 0) {
      throw Exception('Failed to extract $archs for $frameworkBinaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo');
    }
147 148
  }
}
149

150 151 152 153 154 155 156 157 158 159
/// Unpack the release prebuilt engine framework.
class ReleaseUnpackMacOS extends UnpackMacOS {
  const ReleaseUnpackMacOS();

  @override
  String get name => 'release_unpack_macos';

  @override
  List<Source> get inputs => <Source>[
    ...super.inputs,
160
    const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.release),
161 162 163 164 165 166 167 168 169 170 171 172 173
  ];
}

/// Unpack the profile prebuilt engine framework.
class ProfileUnpackMacOS extends UnpackMacOS {
  const ProfileUnpackMacOS();

  @override
  String get name => 'profile_unpack_macos';

  @override
  List<Source> get inputs => <Source>[
    ...super.inputs,
174
    const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.profile),
175 176 177 178 179 180 181 182 183 184 185 186 187
  ];
}

/// Unpack the debug prebuilt engine framework.
class DebugUnpackMacOS extends UnpackMacOS {
  const DebugUnpackMacOS();

  @override
  String get name => 'debug_unpack_macos';

  @override
  List<Source> get inputs => <Source>[
    ...super.inputs,
188
    const Source.artifact(Artifact.flutterMacOSXcframework, mode: BuildMode.debug),
189 190 191
  ];
}

192 193 194 195 196 197 198 199 200 201 202 203
/// Create an App.framework for debug macOS targets.
///
/// This framework needs to exist for the Xcode project to link/bundle,
/// but it isn't actually executed. To generate something valid, we compile a trivial
/// constant.
class DebugMacOSFramework extends Target {
  const DebugMacOSFramework();

  @override
  String get name => 'debug_macos_framework';

  @override
204
  Future<void> build(Environment environment) async {
205
    final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join(
206
        environment.buildDir.path, 'App.framework', 'App'));
207 208 209

    final Iterable<DarwinArch> darwinArchs = environment.defines[kDarwinArchs]
      ?.split(' ')
210
      .map(getDarwinArchForName)
211
      ?? <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64];
212 213

    final Iterable<String> darwinArchArguments =
214
        darwinArchs.expand((DarwinArch arch) => <String>['-arch', arch.name]);
215

216 217 218 219 220
    outputFile.createSync(recursive: true);
    final File debugApp = environment.buildDir.childFile('debug_app.cc')
        ..writeAsStringSync(r'''
static const int Moo = 88;
''');
221
    final RunResult result = await globals.xcode!.clang(<String>[
222 223 224
      '-x',
      'c',
      debugApp.path,
225
      ...darwinArchArguments,
226 227 228
      '-dynamiclib',
      '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
      '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
229
      '-fapplication-extension',
230
      '-install_name', '@rpath/App.framework/App',
231
      '-o', outputFile.path,
232 233 234 235 236
    ]);
    if (result.exitCode != 0) {
      throw Exception('Failed to compile debug App.framework');
    }
  }
237 238

  @override
239
  List<Target> get dependencies => const <Target>[];
240 241 242

  @override
  List<Source> get inputs => const <Source>[
243
    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
244 245 246 247
  ];

  @override
  List<Source> get outputs => const <Source>[
248
    Source.pattern('{BUILD_DIR}/App.framework/App'),
249
  ];
250 251
}

252 253 254 255 256 257 258
class CompileMacOSFramework extends Target {
  const CompileMacOSFramework();

  @override
  String get name => 'compile_macos_framework';

  @override
259
  Future<void> build(Environment environment) async {
260 261
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
262 263
      throw MissingDefineException(kBuildMode, 'compile_macos_framework');
    }
264 265 266 267
    final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
    if (targetPlatformEnvironment == null) {
      throw MissingDefineException(kTargetPlatform, 'kernel_snapshot');
    }
268
    final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
269 270 271
    if (buildMode == BuildMode.debug) {
      throw Exception('precompiled macOS framework only supported in release/profile builds.');
    }
272
    final String buildOutputPath = environment.buildDir.path;
273 274
    final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory];
    final String? splitDebugInfo = environment.defines[kSplitDebugInfo];
275
    final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
276
    final List<String> extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions);
277
    final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
278 279
    final List<DarwinArch> darwinArchs = environment.defines[kDarwinArchs]
      ?.split(' ')
280 281
      .map(getDarwinArchForName)
      .toList()
282
      ?? <DarwinArch>[DarwinArch.x86_64, DarwinArch.arm64];
283 284
    if (targetPlatform != TargetPlatform.darwin) {
      throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.');
285 286
    }

287
    final AOTSnapshotter snapshotter = AOTSnapshotter(
288 289
      fileSystem: environment.fileSystem,
      logger: environment.logger,
290
      xcode: globals.xcode!,
291 292
      artifacts: environment.artifacts,
      processManager: environment.processManager
293
    );
294 295 296 297 298 299

    final List<Future<int>> pending = <Future<int>>[];
    for (final DarwinArch darwinArch in darwinArchs) {
      if (codeSizeDirectory != null) {
        final File codeSizeFile = environment.fileSystem
          .directory(codeSizeDirectory)
300
          .childFile('snapshot.${darwinArch.name}.json');
301 302
        final File precompilerTraceFile = environment.fileSystem
          .directory(codeSizeDirectory)
303
          .childFile('trace.${darwinArch.name}.json');
304 305 306 307 308 309 310
        extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
        extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
      }

      pending.add(snapshotter.build(
        buildMode: buildMode,
        mainPath: environment.buildDir.childFile('app.dill').path,
311
        outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name),
312 313 314 315 316 317 318 319 320 321 322 323 324
        platform: TargetPlatform.darwin,
        darwinArch: darwinArch,
        splitDebugInfo: splitDebugInfo,
        dartObfuscation: dartObfuscation,
        extraGenSnapshotOptions: extraGenSnapshotOptions,
      ));
    }

    final List<int> results = await Future.wait(pending);
    if (results.any((int result) => result != 0)) {
      throw Exception('AOT snapshotter exited with code ${results.join()}');
    }

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    // Combine the app lib into a fat framework.
    await Lipo.create(
      environment,
      darwinArchs,
      relativePath: 'App.framework/App',
      inputDir: buildOutputPath,
    );

    // And combine the dSYM for each architecture too, if it was created.
    await Lipo.create(
      environment,
      darwinArchs,
      relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App',
      inputDir: buildOutputPath,
      // Don't fail if the dSYM wasn't created (i.e. during a debug build).
      skipMissingInputs: true,
    );
342 343 344 345 346 347 348 349 350 351 352
  }

  @override
  List<Target> get dependencies => const <Target>[
    KernelSnapshot(),
  ];

  @override
  List<Source> get inputs => const <Source>[
    Source.pattern('{BUILD_DIR}/app.dill'),
    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'),
353
    Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin),
354 355 356 357 358
  ];

  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{BUILD_DIR}/App.framework/App'),
359
    Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
360 361 362 363 364 365
  ];
}

/// Bundle the flutter assets into the App.framework.
///
/// In debug mode, also include the app.dill and precompiled runtimes.
366 367 368
///
/// See https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
/// for more information on Framework structure.
369 370
abstract class MacOSBundleFlutterAssets extends Target {
  const MacOSBundleFlutterAssets();
371 372

  @override
373
  List<Source> get inputs => const <Source>[
374
    Source.pattern('{BUILD_DIR}/App.framework/App'),
375
    ...IconTreeShaker.inputs,
376 377 378 379
  ];

  @override
  List<Source> get outputs => const <Source>[
380 381
    Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/App'),
    Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/Info.plist'),
382 383 384 385 386
  ];

  @override
  List<String> get depfiles => const <String>[
    'flutter_assets.d',
387
  ];
388 389

  @override
390
  Future<void> build(Environment environment) async {
391 392
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
393 394
      throw MissingDefineException(kBuildMode, 'compile_macos_framework');
    }
395

396
    final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
397 398
    final Directory frameworkRootDirectory = environment
        .outputDir
399 400 401 402 403 404 405 406 407 408 409 410
        .childDirectory('App.framework');
    final Directory outputDirectory = frameworkRootDirectory
        .childDirectory('Versions')
        .childDirectory('A')
        ..createSync(recursive: true);

    // Copy App into framework directory.
    environment.buildDir
      .childDirectory('App.framework')
      .childFile('App')
      .copySync(outputDirectory.childFile('App').path);

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    // Copy the dSYM
    if (environment.buildDir.childDirectory('App.framework.dSYM').existsSync()) {
      final File dsymOutputBinary = environment
        .outputDir
        .childDirectory('App.framework.dSYM')
        .childDirectory('Contents')
        .childDirectory('Resources')
        .childDirectory('DWARF')
        .childFile('App');
      dsymOutputBinary.parent.createSync(recursive: true);
      environment
        .buildDir
        .childDirectory('App.framework.dSYM')
        .childDirectory('Contents')
        .childDirectory('Resources')
        .childDirectory('DWARF')
        .childFile('App')
        .copySync(dsymOutputBinary.path);
    }

431
    // Copy assets into asset directory.
432 433 434 435
    final Directory assetDirectory = outputDirectory
      .childDirectory('Resources')
      .childDirectory('flutter_assets');
    assetDirectory.createSync(recursive: true);
436

437 438 439
    final Depfile assetDepfile = await copyAssets(
      environment,
      assetDirectory,
440
      targetPlatform: TargetPlatform.darwin,
441
      flavor: environment.defines[kFlavor],
442
    );
443
    environment.depFileService.writeToFile(
444
      assetDepfile,
445 446
      environment.buildDir.childFile('flutter_assets.d'),
    );
447

448 449 450 451 452 453 454 455
    // Copy Info.plist template.
    assetDirectory.parent.childFile('Info.plist')
      ..createSync()
      ..writeAsStringSync(r'''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
  <key>CFBundleDevelopmentRegion</key>
  <string>en</string>
  <key>CFBundleExecutable</key>
  <string>App</string>
  <key>CFBundleIdentifier</key>
  <string>io.flutter.flutter.app</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>App</string>
  <key>CFBundlePackageType</key>
  <string>FMWK</string>
  <key>CFBundleShortVersionString</key>
  <string>1.0</string>
  <key>CFBundleVersion</key>
  <string>1.0</string>
472 473 474 475
</dict>
</plist>

''');
476 477 478 479 480
    if (buildMode == BuildMode.debug) {
      // Copy dill file.
      try {
        final File sourceFile = environment.buildDir.childFile('app.dill');
        sourceFile.copySync(assetDirectory.childFile('kernel_blob.bin').path);
481
      } on Exception catch (err) {
482 483 484 485
        throw Exception('Failed to copy app.dill: $err');
      }
      // Copy precompiled runtimes.
      try {
486
        final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData,
487
            platform: TargetPlatform.darwin, mode: BuildMode.debug);
488
        final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData,
489
            platform: TargetPlatform.darwin, mode: BuildMode.debug);
490
        environment.fileSystem.file(vmSnapshotData).copySync(
491
            assetDirectory.childFile('vm_snapshot_data').path);
492
        environment.fileSystem.file(isolateSnapshotData).copySync(
493
            assetDirectory.childFile('isolate_snapshot_data').path);
494
      } on Exception catch (err) {
495 496
        throw Exception('Failed to copy precompiled runtimes: $err');
      }
497
    }
498 499 500
    // Create symlink to current version. These must be relative, from the
    // framework root for Resources/App and from the versions root for
    // Current.
501 502 503 504
    try {
      final Link currentVersion = outputDirectory.parent
          .childLink('Current');
      if (!currentVersion.existsSync()) {
505
        final String linkPath = environment.fileSystem.path.relative(outputDirectory.path,
506
            from: outputDirectory.parent.path);
507
        currentVersion.createSync(linkPath);
508 509 510 511 512
      }
      // Create symlink to current resources.
      final Link currentResources = frameworkRootDirectory
          .childLink('Resources');
      if (!currentResources.existsSync()) {
513
        final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'Resources'),
514 515
            from: frameworkRootDirectory.path);
        currentResources.createSync(linkPath);
516 517 518 519 520
      }
      // Create symlink to current binary.
      final Link currentFramework = frameworkRootDirectory
          .childLink('App');
      if (!currentFramework.existsSync()) {
521
        final String linkPath = environment.fileSystem.path.relative(environment.fileSystem.path.join(currentVersion.path, 'App'),
522 523
            from: frameworkRootDirectory.path);
        currentFramework.createSync(linkPath);
524 525 526
      }
    } on FileSystemException {
      throw Exception('Failed to create symlinks for framework. try removing '
527
        'the "${environment.outputDir.path}" directory and rerunning');
528
    }
529
  }
530 531 532 533 534 535 536 537
}

/// Bundle the debug flutter assets into the App.framework.
class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
  const DebugMacOSBundleFlutterAssets();

  @override
  String get name => 'debug_macos_bundle_flutter_assets';
538 539 540 541

  @override
  List<Target> get dependencies => const <Target>[
    KernelSnapshot(),
542
    DebugMacOSFramework(),
543
    DebugUnpackMacOS(),
544 545 546
  ];

  @override
547 548 549
  List<Source> get inputs => <Source>[
    ...super.inputs,
    const Source.pattern('{BUILD_DIR}/app.dill'),
550 551
    const Source.artifact(Artifact.isolateSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug),
    const Source.artifact(Artifact.vmSnapshotData, platform: TargetPlatform.darwin, mode: BuildMode.debug),
552 553 554
  ];

  @override
555 556
  List<Source> get outputs => <Source>[
    ...super.outputs,
557 558 559
    const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/kernel_blob.bin'),
    const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/vm_snapshot_data'),
    const Source.pattern('{OUTPUT_DIR}/App.framework/Versions/A/Resources/flutter_assets/isolate_snapshot_data'),
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  ];
}

/// Bundle the profile flutter assets into the App.framework.
class ProfileMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
  const ProfileMacOSBundleFlutterAssets();

  @override
  String get name => 'profile_macos_bundle_flutter_assets';

  @override
  List<Target> get dependencies => const <Target>[
    CompileMacOSFramework(),
    ProfileUnpackMacOS(),
  ];
575 576 577 578 579 580 581 582 583 584 585 586

  @override
  List<Source> get inputs => <Source>[
    ...super.inputs,
    const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
  ];

  @override
  List<Source> get outputs => <Source>[
    ...super.outputs,
    const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
  ];
587 588 589 590 591 592 593 594 595 596 597 598 599 600
}


/// Bundle the release flutter assets into the App.framework.
class ReleaseMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
  const ReleaseMacOSBundleFlutterAssets();

  @override
  String get name => 'release_macos_bundle_flutter_assets';

  @override
  List<Target> get dependencies => const <Target>[
    CompileMacOSFramework(),
    ReleaseUnpackMacOS(),
601
  ];
602

603 604 605 606 607 608 609 610 611 612 613 614
  @override
  List<Source> get inputs => <Source>[
    ...super.inputs,
    const Source.pattern('{BUILD_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
  ];

  @override
  List<Source> get outputs => <Source>[
    ...super.outputs,
    const Source.pattern('{OUTPUT_DIR}/App.framework.dSYM/Contents/Resources/DWARF/App'),
  ];

615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
  @override
  Future<void> build(Environment environment) async {
    bool buildSuccess = true;
    try {
      await super.build(environment);
    } catch (_) {  // ignore: avoid_catches_without_on_clauses
      buildSuccess = false;
      rethrow;
    } finally {
      // Send a usage event when the app is being archived from Xcode.
      if (environment.defines[kXcodeAction]?.toLowerCase() == 'install') {
        environment.logger.printTrace('Sending archive event if usage enabled.');
        UsageEvent(
          'assemble',
          'macos-archive',
          label: buildSuccess ? 'success' : 'fail',
          flutterUsage: environment.usage,
        ).send();
633 634 635 636 637
        environment.analytics.send(Event.appleUsageEvent(
          workflow: 'assemble',
          parameter: 'macos-archive',
          result: buildSuccess ? 'success' : 'fail',
        ));
638 639 640 641
      }
    }
  }

642
}