Build xcarchive command (#67598)

diff --git a/dev/devicelab/bin/tasks/ios_content_validation_test.dart b/dev/devicelab/bin/tasks/ios_content_validation_test.dart
index e7f436e..a1964ae 100644
--- a/dev/devicelab/bin/tasks/ios_content_validation_test.dart
+++ b/dev/devicelab/bin/tasks/ios_content_validation_test.dart
@@ -166,6 +166,29 @@
         if (!await localNetworkUsageFound(outputAppPath)) {
           throw TaskResult.failure('Debug bundle is missing NSLocalNetworkUsageDescription');
         }
+
+        section('Clean build');
+
+        await inDirectory(flutterProject.rootPath, () async {
+          await flutter('clean');
+        });
+
+        section('Archive');
+
+        await inDirectory(flutterProject.rootPath, () async {
+          await flutter('build', options: <String>[
+            'xcarchive',
+          ]);
+        });
+
+        checkDirectoryExists(path.join(
+          flutterProject.rootPath,
+          'build',
+          'ios',
+          'archive',
+          'Runner.xcarchive',
+          'Products',
+        ));
       });
 
       return TaskResult.success(null);
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 74a16a0..1410714 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -379,6 +379,12 @@
   @override
   String get deviceBundlePath => _buildAppPath('iphoneos');
 
+  // Xcode uses this path for the final archive bundle location,
+  // not a top-level output directory.
+  // Specifying `build/ios/archive/Runner` will result in `build/ios/archive/Runner.xcarchive`.
+  String get archiveBundlePath
+    => globals.fs.path.join(getIosBuildDirectory(), 'archive', globals.fs.path.withoutExtension(_hostAppBundleName));
+
   String _buildAppPath(String type) {
     return globals.fs.path.join(getIosBuildDirectory(), type, _hostAppBundleName);
   }
diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart
index 2446c9f..19cb541 100644
--- a/packages/flutter_tools/lib/src/commands/build.dart
+++ b/packages/flutter_tools/lib/src/commands/build.dart
@@ -28,6 +28,7 @@
       buildSystem: globals.buildSystem,
       verboseHelp: verboseHelp,
     ));
+    addSubcommand(BuildIOSArchiveCommand(verboseHelp: verboseHelp));
     addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
     addSubcommand(BuildWebCommand(verboseHelp: verboseHelp));
     addSubcommand(BuildMacosCommand(verboseHelp: verboseHelp));
diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart
index a8585db..a3d3438 100644
--- a/packages/flutter_tools/lib/src/commands/build_ios.dart
+++ b/packages/flutter_tools/lib/src/commands/build_ios.dart
@@ -19,24 +19,8 @@
 /// Builds an .app for an iOS app to be used for local testing on an iOS device
 /// or simulator. Can only be run on a macOS host. For producing deployment
 /// .ipas, see https://flutter.dev/docs/deployment/ios.
-class BuildIOSCommand extends BuildSubCommand {
-  BuildIOSCommand({ @required bool verboseHelp }) {
-    addTreeShakeIconsFlag();
-    addSplitDebugInfoOption();
-    addBuildModeFlags(defaultToRelease: true);
-    usesTargetOption();
-    usesFlavorOption();
-    usesPubOption();
-    usesBuildNumberOption();
-    usesBuildNameOption();
-    addDartObfuscationOption();
-    usesDartDefineOption();
-    usesExtraFrontendOptions();
-    addEnableExperimentation(hide: !verboseHelp);
-    addBuildPerformanceFile(hide: !verboseHelp);
-    addBundleSkSLPathOption(hide: !verboseHelp);
-    addNullSafetyModeOptions(hide: !verboseHelp);
-    usesAnalyzeSizeFlag();
+class BuildIOSCommand extends _BuildIOSSubCommand {
+  BuildIOSCommand({ @required bool verboseHelp }) : super(verboseHelp: verboseHelp) {
     argParser
       ..addFlag('config-only',
         help: 'Update the project configuration without performing a build. '
@@ -60,15 +44,75 @@
   final String description = 'Build an iOS application bundle (Mac OS X host only).';
 
   @override
+  final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.build;
+
+  @override
+  bool get forSimulator => boolArg('simulator');
+
+  @override
+  bool get configOnly => boolArg('config-only');
+
+  @override
+  bool get shouldCodesign => boolArg('codesign');
+}
+
+/// Builds an .xcarchive for an iOS app to be generated for App Store submission.
+/// Can only be run on a macOS host.
+/// For producing deployment .ipas, see https://flutter.dev/docs/deployment/ios.
+class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
+  BuildIOSArchiveCommand({ @required bool verboseHelp }) : super(verboseHelp: verboseHelp);
+
+  @override
+  final String name = 'xcarchive';
+
+  @override
+  final String description = 'Build an iOS archive bundle (Mac OS X host only).';
+
+  @override
+  final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive;
+
+  @override
+  final bool forSimulator = false;
+
+  @override
+  final bool configOnly = false;
+
+  @override
+  final bool shouldCodesign = true;
+}
+
+abstract class _BuildIOSSubCommand extends BuildSubCommand {
+  _BuildIOSSubCommand({ @required bool verboseHelp }) {
+    addTreeShakeIconsFlag();
+    addSplitDebugInfoOption();
+    addBuildModeFlags(defaultToRelease: true);
+    usesTargetOption();
+    usesFlavorOption();
+    usesPubOption();
+    usesBuildNumberOption();
+    usesBuildNameOption();
+    addDartObfuscationOption();
+    usesDartDefineOption();
+    usesExtraFrontendOptions();
+    addEnableExperimentation(hide: !verboseHelp);
+    addBuildPerformanceFile(hide: !verboseHelp);
+    addBundleSkSLPathOption(hide: !verboseHelp);
+    addNullSafetyModeOptions(hide: !verboseHelp);
+    usesAnalyzeSizeFlag();
+  }
+
+  @override
   Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
     DevelopmentArtifact.iOS,
   };
 
+  XcodeBuildAction get xcodeBuildAction;
+  bool get forSimulator;
+  bool get configOnly;
+  bool get shouldCodesign;
+
   @override
   Future<FlutterCommandResult> runCommand() async {
-    final bool forSimulator = boolArg('simulator');
-    final bool configOnly = boolArg('config-only');
-    final bool shouldCodesign = boolArg('codesign');
     defaultBuildMode = forSimulator ? BuildMode.debug : BuildMode.release;
     final BuildInfo buildInfo = getBuildInfo();
 
@@ -99,7 +143,11 @@
 
     final String logTarget = forSimulator ? 'simulator' : 'device';
     final String typeName = globals.artifacts.getEngineType(TargetPlatform.ios, buildInfo.mode);
-    globals.printStatus('Building $app for $logTarget ($typeName)...');
+    if (xcodeBuildAction == XcodeBuildAction.build) {
+      globals.printStatus('Building $app for $logTarget ($typeName)...');
+    } else {
+      globals.printStatus('Archiving $app...');
+    }
     final XcodeBuildResult result = await buildXcodeProject(
       app: app,
       buildInfo: buildInfo,
@@ -107,11 +155,12 @@
       buildForDevice: !forSimulator,
       codesign: shouldCodesign,
       configOnly: configOnly,
+      buildAction: xcodeBuildAction,
     );
 
     if (!result.success) {
       await diagnoseXcodeBuildFailure(result, globals.flutterUsage, globals.logger);
-      throwToolExit('Encountered error while building for $logTarget.');
+      throwToolExit('Encountered error while ${xcodeBuildAction.name}ing for $logTarget.');
     }
 
     if (buildInfo.codeSizeDirectory != null) {
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index f5ab4be..4f70b84 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -96,6 +96,7 @@
   bool codesign = true,
   String deviceID,
   bool configOnly = false,
+  XcodeBuildAction buildAction = XcodeBuildAction.build,
 }) async {
   if (!upgradePbxProjWithFlutterAssets(app.project, globals.logger)) {
     return XcodeBuildResult(success: false);
@@ -321,6 +322,14 @@
   buildCommands.add('COMPILER_INDEX_STORE_ENABLE=NO');
   buildCommands.addAll(environmentVariablesAsXcodeBuildSettings(globals.platform));
 
+  if (buildAction == XcodeBuildAction.archive) {
+    buildCommands.addAll(<String>[
+      '-archivePath',
+      globals.fs.path.absolute(app.archiveBundlePath),
+      'archive',
+    ]);
+  }
+
   final Stopwatch sw = Stopwatch()..start();
   initialBuildStatus = globals.logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.slowOperation);
 
@@ -333,13 +342,13 @@
   initialBuildStatus?.cancel();
   initialBuildStatus = null;
   globals.printStatus(
-    'Xcode build done.'.padRight(kDefaultStatusPadding + 1)
+    'Xcode ${buildAction.name} done.'.padRight(kDefaultStatusPadding + 1)
         + getElapsedAsSeconds(sw.elapsed).padLeft(5),
   );
-  globals.flutterUsage.sendTiming('build', 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
+  globals.flutterUsage.sendTiming(buildAction.name, 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
 
   // Run -showBuildSettings again but with the exact same parameters as the
-  // build. showBuildSettings is reported to ocassionally timeout. Here, we give
+  // build. showBuildSettings is reported to occasionally timeout. Here, we give
   // it a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
   // When there is a timeout, we retry once. See issue #35988.
   final List<String> showBuildSettingsCommand = (List<String>
@@ -398,36 +407,42 @@
       ),
     );
   } else {
-    // If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
-    // For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
-    // actual directory will end with 'iphonesimulator' for simulator builds.
-    // The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
-    String targetBuildDir = buildSettings['TARGET_BUILD_DIR'];
-    if (hasWatchCompanion && !buildForDevice) {
-      globals.printTrace('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.');
-      targetBuildDir = targetBuildDir.replaceFirst('iphoneos', 'iphonesimulator');
-    }
-    final String expectedOutputDirectory = globals.fs.path.join(
-      targetBuildDir,
-      buildSettings['WRAPPER_NAME'],
-    );
-
     String outputDir;
-    if (globals.fs.isDirectorySync(expectedOutputDirectory)) {
-      // Copy app folder to a place where other tools can find it without knowing
-      // the BuildInfo.
-      outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/');
-      if (globals.fs.isDirectorySync(outputDir)) {
-        // Previous output directory might have incompatible artifacts
-        // (for example, kernel binary files produced from previous run).
-        globals.fs.directory(outputDir).deleteSync(recursive: true);
+    if (buildAction == XcodeBuildAction.build) {
+      // If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
+      // For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
+      // actual directory will end with 'iphonesimulator' for simulator builds.
+      // The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
+      String targetBuildDir = buildSettings['TARGET_BUILD_DIR'];
+      if (hasWatchCompanion && !buildForDevice) {
+        globals.printTrace('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.');
+        targetBuildDir = targetBuildDir.replaceFirst('iphoneos', 'iphonesimulator');
       }
-      globals.fsUtils.copyDirectorySync(
-        globals.fs.directory(expectedOutputDirectory),
-        globals.fs.directory(outputDir),
+      final String expectedOutputDirectory = globals.fs.path.join(
+        targetBuildDir,
+        buildSettings['WRAPPER_NAME'],
       );
+      if (globals.fs.isDirectorySync(expectedOutputDirectory)) {
+        // Copy app folder to a place where other tools can find it without knowing
+        // the BuildInfo.
+        outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/');
+        if (globals.fs.isDirectorySync(outputDir)) {
+          // Previous output directory might have incompatible artifacts
+          // (for example, kernel binary files produced from previous run).
+          globals.fs.directory(outputDir).deleteSync(recursive: true);
+        }
+        globals.fsUtils.copyDirectorySync(
+          globals.fs.directory(expectedOutputDirectory),
+          globals.fs.directory(outputDir),
+        );
+      } else {
+        globals.printError('Build succeeded but the expected app at $expectedOutputDirectory not found');
+      }
     } else {
-      globals.printError('Build succeeded but the expected app at $expectedOutputDirectory not found');
+      outputDir = '${globals.fs.path.absolute(app.archiveBundlePath)}.xcarchive';
+      if (!globals.fs.isDirectorySync(outputDir)) {
+        globals.printError('Archive succeeded but the expected xcarchive at $outputDir not found');
+      }
     }
     return XcodeBuildResult(
         success: true,
@@ -568,6 +583,24 @@
   }
 }
 
+/// xcodebuild <buildaction> parameter (see man xcodebuild for details).
+///
+/// `clean`, `test`, `analyze`, and `install` are not supported.
+enum XcodeBuildAction { build, archive }
+
+extension XcodeBuildActionExtension on XcodeBuildAction {
+  String get name {
+    switch (this) {
+      case XcodeBuildAction.build:
+        return 'build';
+      case XcodeBuildAction.archive:
+        return 'archive';
+      default:
+        throw UnsupportedError('Unknown Xcode build action');
+    }
+  }
+}
+
 class XcodeBuildResult {
   XcodeBuildResult({
     @required this.success,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_xcarchive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_xcarchive_test.dart
new file mode 100644
index 0000000..b7fa1da
--- /dev/null
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_xcarchive_test.dart
@@ -0,0 +1,216 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/build.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
+import 'package:flutter_tools/src/reporting/reporting.dart';
+import 'package:process/process.dart';
+
+import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/testbed.dart';
+
+class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInterpreter {
+  @override
+  Future<Map<String, String>> getBuildSettings(
+      String projectPath, {
+        String scheme,
+        Duration timeout = const Duration(minutes: 1),
+      }) async {
+    return <String, String>{
+      'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
+      'DEVELOPMENT_TEAM': 'abc',
+    };
+  }
+}
+
+final Platform macosPlatform = FakePlatform(
+  operatingSystem: 'macos',
+  environment: <String, String>{
+    'FLUTTER_ROOT': '/',
+  }
+);
+final Platform notMacosPlatform = FakePlatform(
+  operatingSystem: 'linux',
+  environment: <String, String>{
+    'FLUTTER_ROOT': '/',
+  }
+);
+
+void main() {
+  FileSystem fileSystem;
+  Usage usage;
+
+  setUpAll(() {
+    Cache.disableLocking();
+  });
+
+  setUp(() {
+    fileSystem = MemoryFileSystem.test();
+    usage = Usage.test();
+  });
+
+  // Sets up the minimal mock project files necessary to look like a Flutter project.
+  void createCoreMockProjectFiles() {
+    fileSystem.file('pubspec.yaml').createSync();
+    fileSystem.file('.packages').createSync();
+    fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
+  }
+
+  // Sets up the minimal mock project files necessary for iOS builds to succeed.
+  void createMinimalMockProjectFiles() {
+    fileSystem.directory(fileSystem.path.join('ios', 'Runner.xcodeproj')).createSync(recursive: true);
+    fileSystem.directory(fileSystem.path.join('ios', 'Runner.xcworkspace')).createSync(recursive: true);
+    fileSystem.file(fileSystem.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj')).createSync();
+    createCoreMockProjectFiles();
+  }
+
+  const FakeCommand xattrCommand = FakeCommand(command: <String>[
+    'xattr', '-r', '-d', 'com.apple.FinderInfo', '/ios'
+  ]);
+
+  // Creates a FakeCommand for the xcodebuild call to build the app
+  // in the given configuration.
+  FakeCommand setUpMockXcodeBuildHandler({ bool verbose = false, bool showBuildSettings = false, void Function() onRun }) {
+    return FakeCommand(
+      command: <String>[
+        '/usr/bin/env',
+        'xcrun',
+        'xcodebuild',
+        '-configuration', 'Release',
+        if (verbose)
+          'VERBOSE_SCRIPT_LOGGING=YES'
+        else
+          '-quiet',
+        '-workspace', 'Runner.xcworkspace',
+        '-scheme', 'Runner',
+        'BUILD_DIR=/build/ios',
+        '-sdk', 'iphoneos',
+        'FLUTTER_SUPPRESS_ANALYTICS=true',
+        'COMPILER_INDEX_STORE_ENABLE=NO',
+        '-archivePath', '/build/ios/archive/Runner',
+        'archive',
+        if (showBuildSettings)
+          '-showBuildSettings',
+      ],
+      stdout: 'STDOUT STUFF',
+      onRun: onRun,
+    );
+  }
+
+  testUsingContext('xcarchive build fails when there is no ios project', () async {
+    final BuildCommand command = BuildCommand();
+    createCoreMockProjectFiles();
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'xcarchive', '--no-pub']
+    ), throwsToolExit(message: 'Application not configured for iOS'));
+  }, overrides: <Type, Generator>{
+    Platform: () => macosPlatform,
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
+  });
+
+  testUsingContext('xcarchive build fails on non-macOS platform', () async {
+    final BuildCommand command = BuildCommand();
+    fileSystem.file('pubspec.yaml').createSync();
+    fileSystem.file('.packages').createSync();
+    fileSystem.file(fileSystem.path.join('lib', 'main.dart'))
+      .createSync(recursive: true);
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'xcarchive', '--no-pub']
+    ), throwsToolExit());
+  }, overrides: <Type, Generator>{
+    Platform: () => notMacosPlatform,
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
+  });
+
+  testUsingContext('xcarchive build invokes xcode build', () async {
+    final BuildCommand command = BuildCommand();
+    createMinimalMockProjectFiles();
+
+    await createTestCommandRunner(command).run(
+      const <String>['build', 'xcarchive', '--no-pub']
+    );
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
+      xattrCommand,
+      setUpMockXcodeBuildHandler(),
+      setUpMockXcodeBuildHandler(showBuildSettings: true),
+    ]),
+    Platform: () => macosPlatform,
+    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
+  });
+
+  testUsingContext('xcarchive build invokes xcode build with verbosity', () async {
+    final BuildCommand command = BuildCommand();
+    createMinimalMockProjectFiles();
+
+    await createTestCommandRunner(command).run(
+      const <String>['build', 'xcarchive', '--no-pub', '-v']
+    );
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
+      xattrCommand,
+      setUpMockXcodeBuildHandler(verbose: true),
+      setUpMockXcodeBuildHandler(verbose: true, showBuildSettings: true),
+    ]),
+    Platform: () => macosPlatform,
+    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
+  });
+
+  testUsingContext('Performs code size analysis and sends analytics', () async {
+    final BuildCommand command = BuildCommand();
+    createMinimalMockProjectFiles();
+
+    fileSystem.file('build/ios/Release-iphoneos/Runner.app/Frameworks/App.framework/App')
+      ..createSync(recursive: true)
+      ..writeAsBytesSync(List<int>.generate(10000, (int index) => 0));
+
+    // Capture Usage.test() events.
+    final StringBuffer buffer = await capturedConsolePrint(() =>
+      createTestCommandRunner(command).run(
+        const <String>['build', 'xcarchive', '--no-pub', '--analyze-size']
+      )
+    );
+
+    expect(testLogger.statusText, contains('A summary of your iOS bundle analysis can be found at'));
+    expect(buffer.toString(), contains('event {category: code-size-analysis, action: ios, label: null, value: null, cd33: '));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
+      xattrCommand,
+      setUpMockXcodeBuildHandler(onRun: () {
+        fileSystem.file('build/flutter_size_01/snapshot.arm64.json')
+          ..createSync(recursive: true)
+          ..writeAsStringSync('''[
+{
+  "l": "dart:_internal",
+  "c": "SubListIterable",
+  "n": "[Optimized] skip",
+  "s": 2400
+}
+          ]''');
+        fileSystem.file('build/flutter_size_01/trace.arm64.json')
+          ..createSync(recursive: true)
+          ..writeAsStringSync('{}');
+      }),
+      setUpMockXcodeBuildHandler(showBuildSettings: true),
+    ]),
+    Platform: () => macosPlatform,
+    FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform),
+    Usage: () => usage,
+    XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
+  });
+}
diff --git a/packages/flutter_tools/test/general.shard/commands/build_test.dart b/packages/flutter_tools/test/general.shard/commands/build_test.dart
index 0c0acfd..a7c0e8e 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_test.dart
@@ -29,6 +29,7 @@
       BuildWebCommand(verboseHelp: false),
       BuildApkCommand(verboseHelp: false),
       BuildIOSCommand(verboseHelp: false),
+      BuildIOSArchiveCommand(verboseHelp: false),
       BuildAppBundleCommand(verboseHelp: false),
       BuildFuchsiaCommand(verboseHelp: false),
       BuildAarCommand(verboseHelp: false),