Add usage event when iOS app is archived (#108643)

This commit is contained in:
Jenn Magder 2022-07-29 15:44:06 -07:00 committed by GitHub
parent c7498f607b
commit 7f1a8f7948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 127 additions and 23 deletions

View File

@ -17,9 +17,16 @@ Future<void> main() async {
section('Archive'); section('Archive');
await inDirectory(flutterProject.rootPath, () async { await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[ final String output = await evalFlutter('build', options: <String>[
'xcarchive', 'xcarchive',
'-v',
]); ]);
// Note this isBot so usage won't actually be sent,
// this log line is printed whenever the app is archived.
if (!output.contains('Sending archive event if usage enabled')) {
throw TaskResult.failure('Usage archive event not sent');
}
}); });
final String archivePath = path.join( final String archivePath = path.join(

View File

@ -366,6 +366,7 @@ class Context {
'-dTrackWidgetCreation=${environment['TRACK_WIDGET_CREATION'] ?? ''}', '-dTrackWidgetCreation=${environment['TRACK_WIDGET_CREATION'] ?? ''}',
'-dDartObfuscation=${environment['DART_OBFUSCATION'] ?? ''}', '-dDartObfuscation=${environment['DART_OBFUSCATION'] ?? ''}',
'-dEnableBitcode=$bitcodeFlag', '-dEnableBitcode=$bitcodeFlag',
'-dAction=${environment['ACTION'] ?? ''}',
'--ExtraGenSnapshotOptions=${environment['EXTRA_GEN_SNAPSHOT_OPTIONS'] ?? ''}', '--ExtraGenSnapshotOptions=${environment['EXTRA_GEN_SNAPSHOT_OPTIONS'] ?? ''}',
'--DartDefines=${environment['DART_DEFINES'] ?? ''}', '--DartDefines=${environment['DART_DEFINES'] ?? ''}',
'--ExtraFrontEndOptions=${environment['EXTRA_FRONT_END_OPTIONS'] ?? ''}', '--ExtraFrontEndOptions=${environment['EXTRA_FRONT_END_OPTIONS'] ?? ''}',

View File

@ -950,6 +950,11 @@ const String kBuildName = 'BuildName';
/// The define to pass build number /// The define to pass build number
const String kBuildNumber = 'BuildNumber'; const String kBuildNumber = 'BuildNumber';
/// The action Xcode is taking.
///
/// Will be "build" when building and "install" when archiving.
const String kXcodeAction = 'Action';
final Converter<String, String> _defineEncoder = utf8.encoder.fuse(base64.encoder); final Converter<String, String> _defineEncoder = utf8.encoder.fuse(base64.encoder);
final Converter<String, String> _defineDecoder = base64.decoder.fuse(utf8.decoder); final Converter<String, String> _defineDecoder = base64.decoder.fuse(utf8.decoder);

View File

@ -17,6 +17,7 @@ import '../base/platform.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../reporting/reporting.dart';
import 'exceptions.dart'; import 'exceptions.dart';
import 'file_store.dart'; import 'file_store.dart';
import 'source.dart'; import 'source.dart';
@ -332,6 +333,7 @@ class Environment {
required Artifacts artifacts, required Artifacts artifacts,
required ProcessManager processManager, required ProcessManager processManager,
required Platform platform, required Platform platform,
required Usage usage,
String? engineVersion, String? engineVersion,
required bool generateDartPluginRegistry, required bool generateDartPluginRegistry,
Directory? buildDir, Directory? buildDir,
@ -372,6 +374,7 @@ class Environment {
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
platform: platform, platform: platform,
usage: usage,
engineVersion: engineVersion, engineVersion: engineVersion,
inputs: inputs, inputs: inputs,
generateDartPluginRegistry: generateDartPluginRegistry, generateDartPluginRegistry: generateDartPluginRegistry,
@ -392,6 +395,7 @@ class Environment {
Map<String, String> inputs = const <String, String>{}, Map<String, String> inputs = const <String, String>{},
String? engineVersion, String? engineVersion,
Platform? platform, Platform? platform,
Usage? usage,
bool generateDartPluginRegistry = false, bool generateDartPluginRegistry = false,
required FileSystem fileSystem, required FileSystem fileSystem,
required Logger logger, required Logger logger,
@ -411,6 +415,7 @@ class Environment {
artifacts: artifacts, artifacts: artifacts,
processManager: processManager, processManager: processManager,
platform: platform ?? FakePlatform(), platform: platform ?? FakePlatform(),
usage: usage ?? TestUsage(),
engineVersion: engineVersion, engineVersion: engineVersion,
generateDartPluginRegistry: generateDartPluginRegistry, generateDartPluginRegistry: generateDartPluginRegistry,
); );
@ -429,6 +434,7 @@ class Environment {
required this.logger, required this.logger,
required this.fileSystem, required this.fileSystem,
required this.artifacts, required this.artifacts,
required this.usage,
this.engineVersion, this.engineVersion,
required this.inputs, required this.inputs,
required this.generateDartPluginRegistry, required this.generateDartPluginRegistry,
@ -509,6 +515,8 @@ class Environment {
final FileSystem fileSystem; final FileSystem fileSystem;
final Usage usage;
/// The version of the current engine, or `null` if built with a local engine. /// The version of the current engine, or `null` if built with a local engine.
final String? engineVersion; final String? engineVersion;

View File

@ -13,6 +13,7 @@ import '../../build_info.dart';
import '../../globals.dart' as globals show xcode; import '../../globals.dart' as globals show xcode;
import '../../macos/xcode.dart'; import '../../macos/xcode.dart';
import '../../project.dart'; import '../../project.dart';
import '../../reporting/reporting.dart';
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
@ -570,6 +571,30 @@ class ReleaseIosApplicationBundle extends IosAssetBundle {
List<Target> get dependencies => const <Target>[ List<Target> get dependencies => const <Target>[
AotAssemblyRelease(), AotAssemblyRelease(),
]; ];
@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.
// Since assemble is run during a `flutter build`/`run` as well as an out-of-band
// archive command from Xcode, this is a more accurate count than `flutter build ipa` alone.
if (environment.defines[kXcodeAction]?.toLowerCase() == 'install') {
environment.logger.printTrace('Sending archive event if usage enabled.');
UsageEvent(
'assemble',
'ios-archive',
label: buildSuccess ? 'success' : 'fail',
flutterUsage: environment.usage,
).send();
}
}
}
} }
/// Create an App.framework for debug iOS targets. /// Create an App.framework for debug iOS targets.

View File

@ -65,6 +65,7 @@ class BundleBuilder {
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
usage: globals.flutterUsage,
platform: globals.platform, platform: globals.platform,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );

View File

@ -238,6 +238,7 @@ class AssembleCommand extends FlutterCommand {
fileSystem: globals.fs, fileSystem: globals.fs,
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
usage: globals.flutterUsage,
platform: globals.platform, platform: globals.platform,
engineVersion: artifacts.isLocalEngine engineVersion: artifacts.isLocalEngine
? null ? null

View File

@ -433,6 +433,7 @@ end
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
engineVersion: globals.artifacts!.isLocalEngine engineVersion: globals.artifacts!.isLocalEngine
? null ? null
: globals.flutterVersion.engineRevision, : globals.flutterVersion.engineRevision,

View File

@ -204,6 +204,7 @@ end
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
engineVersion: globals.artifacts!.isLocalEngine ? null : globals.flutterVersion.engineRevision, engineVersion: globals.artifacts!.isLocalEngine ? null : globals.flutterVersion.engineRevision,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );

View File

@ -538,6 +538,7 @@ abstract class CreateBase extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()), outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
projectDir: project.directory, projectDir: project.directory,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );

View File

@ -124,6 +124,7 @@ class PackagesGetCommand extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()), outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
projectDir: flutterProject.directory, projectDir: flutterProject.directory,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );
@ -332,6 +333,7 @@ class PackagesInteractiveGetCommand extends FlutterCommand {
outputDir: globals.fs.directory(getBuildDirectory()), outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
projectDir: flutterProject.directory, projectDir: flutterProject.directory,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );

View File

@ -1217,6 +1217,7 @@ abstract class ResidentRunner extends ResidentHandlers {
outputDir: globals.fs.directory(getBuildDirectory()), outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
projectDir: globals.fs.currentDirectory, projectDir: globals.fs.currentDirectory,
generateDartPluginRegistry: generateDartPluginRegistry, generateDartPluginRegistry: generateDartPluginRegistry,
defines: <String, String>{ defines: <String, String>{

View File

@ -1343,6 +1343,7 @@ abstract class FlutterCommand extends Command<void> {
outputDir: globals.fs.directory(getBuildDirectory()), outputDir: globals.fs.directory(getBuildDirectory()),
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
projectDir: project.directory, projectDir: project.directory,
generateDartPluginRegistry: true, generateDartPluginRegistry: true,
); );

View File

@ -72,6 +72,7 @@ Future<void> buildWeb(
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage,
cacheDir: globals.cache.getRoot(), cacheDir: globals.cache.getRoot(),
engineVersion: globals.artifacts!.isLocalEngine engineVersion: globals.artifacts!.isLocalEngine
? null ? null

View File

@ -9,7 +9,6 @@ import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/deferred_component.dart'; import 'package:flutter_tools/src/base/deferred_component.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
@ -23,21 +22,13 @@ void main() {
Environment createEnvironment() { Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' }; final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' };
final Environment result = Environment( final Environment result = Environment.test(
outputDir: fileSystem.directory('/output'), fileSystem.directory('/project'),
buildDir: fileSystem.directory('/build'),
projectDir: fileSystem.directory('/project'),
defines: defines, defines: defines,
inputs: <String, String>{},
cacheDir: fileSystem.directory('/cache'),
flutterRootDir: fileSystem.directory('/flutter_root'),
artifacts: Artifacts.test(), artifacts: Artifacts.test(),
fileSystem: fileSystem, fileSystem: fileSystem,
logger: logger, logger: logger,
processManager: FakeProcessManager.any(), processManager: FakeProcessManager.any(),
platform: FakePlatform(),
engineVersion: 'invalidEngineVersion',
generateDartPluginRegistry: false,
); );
return result; return result;
} }

View File

@ -12,9 +12,11 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/ios.dart'; import 'package:flutter_tools/src/build_system/targets/ios.dart';
import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import '../../../src/common.dart'; import '../../../src/common.dart';
import '../../../src/context.dart'; import '../../../src/context.dart';
import '../../../src/fake_process_manager.dart';
final Platform macPlatform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{}); final Platform macPlatform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{});
@ -42,12 +44,14 @@ void main() {
late FakeProcessManager processManager; late FakeProcessManager processManager;
late Artifacts artifacts; late Artifacts artifacts;
late BufferLogger logger; late BufferLogger logger;
late TestUsage usage;
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.empty(); processManager = FakeProcessManager.empty();
logger = BufferLogger.test(); logger = BufferLogger.test();
artifacts = Artifacts.test(); artifacts = Artifacts.test();
usage = TestUsage();
environment = Environment.test( environment = Environment.test(
fileSystem.currentDirectory, fileSystem.currentDirectory,
defines: <String, String>{ defines: <String, String>{
@ -59,6 +63,7 @@ void main() {
logger: logger, logger: logger,
fileSystem: fileSystem, fileSystem: fileSystem,
engineVersion: '2', engineVersion: '2',
usage: usage,
); );
}); });
@ -216,9 +221,10 @@ void main() {
expect(assetDirectory.childFile('io.flutter.shaders.json').readAsStringSync(), '{"data":{"A":"B"}}'); expect(assetDirectory.childFile('io.flutter.shaders.json').readAsStringSync(), '{"data":{"A":"B"}}');
}); });
testUsingContext('ReleaseIosApplicationBundle', () async { testUsingContext('ReleaseIosApplicationBundle build', () async {
environment.defines[kBuildMode] = 'release'; environment.defines[kBuildMode] = 'release';
environment.defines[kCodesignIdentity] = 'ABC123'; environment.defines[kCodesignIdentity] = 'ABC123';
environment.defines[kXcodeAction] = 'build';
// Project info // Project info
fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello'); fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
@ -247,7 +253,7 @@ void main() {
); );
await const ReleaseIosApplicationBundle().build(environment); await const ReleaseIosApplicationBundle().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
expect(frameworkDirectoryBinary, exists); expect(frameworkDirectoryBinary, exists);
expect(frameworkDirectory.childFile('Info.plist'), exists); expect(frameworkDirectory.childFile('Info.plist'), exists);
@ -257,6 +263,53 @@ void main() {
expect(assetDirectory.childFile('AssetManifest.json'), exists); expect(assetDirectory.childFile('AssetManifest.json'), exists);
expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists)); expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists));
expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists)); expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists));
expect(usage.events, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => macPlatform,
});
testUsingContext('ReleaseIosApplicationBundle sends archive success event', () async {
environment.defines[kBuildMode] = 'release';
environment.defines[kXcodeAction] = 'install';
fileSystem.file(fileSystem.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
.createSync(recursive: true);
environment.buildDir
.childDirectory('App.framework')
.childFile('App')
.createSync(recursive: true);
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
processManager.addCommand(
FakeCommand(command: <String>[
'codesign',
'--force',
'--sign',
'-',
frameworkDirectoryBinary.path,
]),
);
await const ReleaseIosApplicationBundle().build(environment);
expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'success')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Platform: () => macPlatform,
});
testUsingContext('ReleaseIosApplicationBundle sends archive fail event', () async {
environment.defines[kBuildMode] = 'release';
environment.defines[kXcodeAction] = 'install';
// Throws because the project files are not set up.
await expectLater(() => const ReleaseIosApplicationBundle().build(environment),
throwsA(const TypeMatcher<FileSystemException>()));
expect(usage.events, contains(const TestUsageEvent('assemble', 'ios-archive', label: 'fail')));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => processManager, ProcessManager: () => processManager,
@ -286,7 +339,7 @@ void main() {
contains('release/profile builds are only supported for physical devices.'), contains('release/profile builds are only supported for physical devices.'),
) )
)); ));
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => processManager, ProcessManager: () => processManager,
@ -313,7 +366,7 @@ void main() {
'description', 'description',
contains('required define SdkRoot but it was not provided'), contains('required define SdkRoot but it was not provided'),
))); )));
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => processManager, ProcessManager: () => processManager,
@ -414,7 +467,7 @@ void main() {
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter')); expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('fails when frameworks missing', () async { testWithoutContext('fails when frameworks missing', () async {
@ -565,7 +618,7 @@ void main() {
expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter')); expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('thins fat framework', () async { testWithoutContext('thins fat framework', () async {
@ -613,7 +666,7 @@ void main() {
]); ]);
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('fails when bitcode strip fails', () async { testWithoutContext('fails when bitcode strip fails', () async {
@ -656,7 +709,7 @@ void main() {
)), )),
); );
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('strips framework', () async { testWithoutContext('strips framework', () async {
@ -685,7 +738,7 @@ void main() {
]); ]);
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('fails when codesign fails', () async { testWithoutContext('fails when codesign fails', () async {
@ -730,7 +783,7 @@ void main() {
)), )),
); );
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('codesigns framework', () async { testWithoutContext('codesigns framework', () async {
@ -767,7 +820,7 @@ void main() {
]); ]);
await const DebugUnpackIOS().build(environment); await const DebugUnpackIOS().build(environment);
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager, hasNoRemainingExpectations);
}); });
}); });
} }

View File

@ -29,6 +29,7 @@ void main() {
final TestContext context = TestContext( final TestContext context = TestContext(
<String>['build'], <String>['build'],
<String, String>{ <String, String>{
'ACTION': 'build',
'BUILT_PRODUCTS_DIR': buildDir.path, 'BUILT_PRODUCTS_DIR': buildDir.path,
'ENABLE_BITCODE': 'YES', 'ENABLE_BITCODE': 'YES',
'FLUTTER_ROOT': flutterRoot.path, 'FLUTTER_ROOT': flutterRoot.path,
@ -51,6 +52,7 @@ void main() {
'-dTrackWidgetCreation=', '-dTrackWidgetCreation=',
'-dDartObfuscation=', '-dDartObfuscation=',
'-dEnableBitcode=', '-dEnableBitcode=',
'-dAction=build',
'--ExtraGenSnapshotOptions=', '--ExtraGenSnapshotOptions=',
'--DartDefines=', '--DartDefines=',
'--ExtraFrontEndOptions=', '--ExtraFrontEndOptions=',
@ -104,6 +106,7 @@ void main() {
'-dTrackWidgetCreation=', '-dTrackWidgetCreation=',
'-dDartObfuscation=', '-dDartObfuscation=',
'-dEnableBitcode=', '-dEnableBitcode=',
'-dAction=',
'--ExtraGenSnapshotOptions=', '--ExtraGenSnapshotOptions=',
'--DartDefines=', '--DartDefines=',
'--ExtraFrontEndOptions=', '--ExtraFrontEndOptions=',
@ -179,6 +182,7 @@ void main() {
'-dTrackWidgetCreation=$trackWidgetCreation', '-dTrackWidgetCreation=$trackWidgetCreation',
'-dDartObfuscation=$dartObfuscation', '-dDartObfuscation=$dartObfuscation',
'-dEnableBitcode=true', '-dEnableBitcode=true',
'-dAction=install',
'--ExtraGenSnapshotOptions=$extraGenSnapshotOptions', '--ExtraGenSnapshotOptions=$extraGenSnapshotOptions',
'--DartDefines=$dartDefines', '--DartDefines=$dartDefines',
'--ExtraFrontEndOptions=$extraFrontEndOptions', '--ExtraFrontEndOptions=$extraFrontEndOptions',