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

import '../../artifacts.dart';
6
import '../../base/build.dart';
7 8
import '../../base/file_system.dart';
import '../../base/io.dart';
9
import '../../base/process.dart';
10
import '../../build_info.dart';
11
import '../../globals.dart' as globals show xcode;
12
import '../../reporting/reporting.dart';
13
import '../build_system.dart';
14
import '../depfile.dart';
15
import '../exceptions.dart';
16
import 'assets.dart';
17
import 'common.dart';
18
import 'icon_tree_shaker.dart';
19
import 'shader_compiler.dart';
20

21
/// Copy the macOS framework to the correct copy dir by invoking 'rsync'.
22
///
23
/// This class is abstract to share logic between the three concrete
24 25
/// implementations. The shelling out is done to avoid complications with
/// preserving special files (e.g., symbolic links) in the framework structure.
26
///
27 28 29 30 31
/// The real implementations are:
///   * [DebugUnpackMacOS]
///   * [ProfileUnpackMacOS]
///   * [ReleaseUnpackMacOS]
abstract class UnpackMacOS extends Target {
32
  const UnpackMacOS();
33

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

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

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

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

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

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

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

86 87 88 89 90 91 92 93 94 95 96 97 98
  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();
      }
    }
  }

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

113
    final ProcessResult verifyResult = await environment.processManager.run(<String>[
114 115 116
      'lipo',
      frameworkBinaryPath,
      '-verify_arch',
117
      ...archList,
118 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
    ]);

    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');
    }
146 147
  }
}
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
/// 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,
    const Source.artifact(Artifact.flutterMacOSFramework, mode: BuildMode.release),
  ];
}

/// 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,
    const Source.artifact(Artifact.flutterMacOSFramework, mode: BuildMode.profile),
  ];
}

/// 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,
    const Source.artifact(Artifact.flutterMacOSFramework, mode: BuildMode.debug),
  ];
}

191 192 193 194 195 196 197 198 199 200 201 202
/// 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
203
  Future<void> build(Environment environment) async {
204
    final File outputFile = environment.fileSystem.file(environment.fileSystem.path.join(
205
        environment.buildDir.path, 'App.framework', 'App'));
206 207 208

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

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

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

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

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

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

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

  @override
  String get name => 'compile_macos_framework';

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

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

    final List<Future<int>> pending = <Future<int>>[];
    for (final DarwinArch darwinArch in darwinArchs) {
      if (codeSizeDirectory != null) {
        final File codeSizeFile = environment.fileSystem
          .directory(codeSizeDirectory)
299
          .childFile('snapshot.${darwinArch.name}.json');
300 301
        final File precompilerTraceFile = environment.fileSystem
          .directory(codeSizeDirectory)
302
          .childFile('trace.${darwinArch.name}.json');
303 304 305 306 307 308 309
        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,
310
        outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name),
311 312 313 314 315 316 317 318 319 320 321 322 323
        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()}');
    }

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
    // 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,
    );
341 342 343 344 345 346 347 348 349 350 351
  }

  @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'),
352
    Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin),
353 354 355 356 357
  ];

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

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

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

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

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

  @override
389
  Future<void> build(Environment environment) async {
390 391
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
392 393
      throw MissingDefineException(kBuildMode, 'compile_macos_framework');
    }
394
    final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
395 396
    final Directory frameworkRootDirectory = environment
        .outputDir
397 398 399 400 401 402 403 404 405 406 407 408
        .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);

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
    // 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);
    }

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

435 436 437
    final Depfile assetDepfile = await copyAssets(
      environment,
      assetDirectory,
438
      targetPlatform: TargetPlatform.darwin,
439
      shaderTarget: ShaderTarget.sksl,
440
    );
441
    environment.depFileService.writeToFile(
442
      assetDepfile,
443 444
      environment.buildDir.childFile('flutter_assets.d'),
    );
445

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    // 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>
	<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>
</dict>
</plist>

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

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

  @override
  String get name => 'debug_macos_bundle_flutter_assets';
536 537 538 539

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

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

  @override
553 554
  List<Source> get outputs => <Source>[
    ...super.outputs,
555 556 557
    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'),
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
  ];
}

/// 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(),
  ];
573 574 575 576 577 578 579 580 581 582 583 584

  @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'),
  ];
585 586 587 588 589 590 591 592 593 594 595 596 597 598
}


/// 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(),
599
  ];
600

601 602 603 604 605 606 607 608 609 610 611 612
  @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'),
  ];

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
  @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();
      }
    }
  }

635
}