blob: 2fc0003e722d240e39ced5c1ec726253774808c1 [file] [log] [blame]
// 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 'dart:async';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart';
import '../android/android_device.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../daemon.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../tracing.dart';
import '../vmservice.dart';
import '../web/web_runner.dart';
import 'daemon.dart';
/// Shared logic between `flutter run` and `flutter drive` commands.
abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
RunCommandBase({ required bool verboseHelp }) {
addBuildModeFlags(verboseHelp: verboseHelp, defaultToRelease: false);
usesDartDefineOption();
usesFlavorOption();
usesWebRendererOption();
addNativeNullAssertions(hide: !verboseHelp);
addBundleSkSLPathOption(hide: !verboseHelp);
usesApplicationBinaryOption();
argParser
..addFlag('trace-startup',
negatable: false,
help: 'Trace application startup, then exit, saving the trace to a file. '
'By default, this will be saved in the "build" directory. If the '
'FLUTTER_TEST_OUTPUTS_DIR environment variable is set, the file '
'will be written there instead.',
)
..addFlag('cache-startup-profile',
help: 'Caches the CPU profile collected before the first frame for startup '
'analysis.',
)
..addFlag('verbose-system-logs',
negatable: false,
help: 'Include verbose logging from the Flutter engine.',
)
..addFlag('cache-sksl',
negatable: false,
help: 'Cache the shader in the SkSL format instead of in binary or GLSL formats.',
)
..addFlag('dump-skp-on-shader-compilation',
negatable: false,
help: 'Automatically dump the skp that triggers new shader compilations. '
'This is useful for writing custom ShaderWarmUp to reduce jank. '
'By default, this is not enabled as it introduces significant overhead. '
'This is only available in profile or debug builds.',
)
..addFlag('purge-persistent-cache',
negatable: false,
help: 'Removes all existing persistent caches. This allows reproducing '
'shader compilation jank that normally only happens the first time '
'an app is run, or for reliable testing of compilation jank fixes '
'(e.g. shader warm-up).',
)
..addOption('route',
help: 'Which route to load when running the app.',
)
..addOption('vmservice-out-file',
help: 'A file to write the attached vmservice URL to after an '
'application is started.',
valueHelp: 'project/example/out.txt',
hide: !verboseHelp,
)
..addFlag('disable-service-auth-codes',
negatable: false,
hide: !verboseHelp,
help: '(deprecated) Allow connections to the VM service without using authentication codes. '
'(Not recommended! This can open your device to remote code execution attacks!)'
)
..addFlag('start-paused',
defaultsTo: startPausedDefault,
help: 'Start in a paused mode and wait for a debugger to connect.',
)
..addOption('dart-flags',
hide: !verboseHelp,
help: 'Pass a list of comma separated flags to the Dart instance at '
'application startup. Flags passed through this option must be '
'present on the allowlist defined within the Flutter engine. If '
'a disallowed flag is encountered, the process will be '
'terminated immediately.\n\n'
'This flag is not available on the stable channel and is only '
'applied in debug and profile modes. This option should only '
'be used for experiments and should not be used by typical users.'
)
..addFlag('endless-trace-buffer',
negatable: false,
help: 'Enable tracing to an infinite buffer, instead of a ring buffer. '
'This is useful when recording large traces. To use an endless buffer to '
'record startup traces, combine this with "--trace-startup".',
)
..addFlag('trace-systrace',
negatable: false,
help: 'Enable tracing to the system tracer. This is only useful on '
'platforms where such a tracer is available (Android, iOS, '
'macOS and Fuchsia).',
)
..addFlag('trace-skia',
negatable: false,
help: 'Enable tracing of Skia code. This is useful when debugging '
'the raster thread (formerly known as the GPU thread). '
'By default, Flutter will not log Skia code, as it introduces significant '
'overhead that may affect recorded performance metrics in a misleading way.',
)
..addOption('trace-allowlist',
hide: !verboseHelp,
help: 'Filters out all trace events except those that are specified in '
'this comma separated list of allowed prefixes.',
valueHelp: 'foo,bar',
)
..addOption('trace-skia-allowlist',
hide: !verboseHelp,
help: 'Filters out all Skia trace events except those that are specified in '
'this comma separated list of allowed prefixes.',
valueHelp: 'skia.gpu,skia.shaders',
)
..addFlag('enable-dart-profiling',
defaultsTo: true,
help: 'Whether the Dart VM sampling CPU profiler is enabled. This flag '
'is only meaningnful in debug and profile builds.',
)
..addFlag('enable-software-rendering',
negatable: false,
help: 'Enable rendering using the Skia software backend. '
'This is useful when testing Flutter on emulators. By default, '
'Flutter will attempt to either use OpenGL or Vulkan and fall back '
'to software when neither is available.',
)
..addFlag('skia-deterministic-rendering',
negatable: false,
help: 'When combined with "--enable-software-rendering", this should provide completely '
'deterministic (i.e. reproducible) Skia rendering. This is useful for testing purposes '
'(e.g. when comparing screenshots).',
)
..addMultiOption('dart-entrypoint-args',
abbr: 'a',
help: 'Pass a list of arguments to the Dart entrypoint at application '
'startup. By default this is main(List<String> args). Specify '
'this option multiple times each with one argument to pass '
'multiple arguments to the Dart entrypoint. Currently this is '
'only supported on desktop platforms.',
)
..addFlag('uninstall-first',
hide: !verboseHelp,
help: 'Uninstall previous versions of the app on the device '
'before reinstalling. Currently only supported on iOS.',
);
usesWebOptions(verboseHelp: verboseHelp);
usesTargetOption();
usesPortOptions(verboseHelp: verboseHelp);
usesIpv6Flag(verboseHelp: verboseHelp);
usesPubOption();
usesTrackWidgetCreation(verboseHelp: verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
usesDeviceUserOption();
usesDeviceTimeoutOption();
addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions(verboseHelp: verboseHelp);
addAndroidSpecificBuildOptions(hide: !verboseHelp);
usesFatalWarningsOption(verboseHelp: verboseHelp);
addEnableImpellerFlag(verboseHelp: verboseHelp);
}
bool get traceStartup => boolArgDeprecated('trace-startup');
bool get enableDartProfiling => boolArgDeprecated('enable-dart-profiling');
bool get cacheSkSL => boolArgDeprecated('cache-sksl');
bool get dumpSkpOnShaderCompilation => boolArgDeprecated('dump-skp-on-shader-compilation');
bool get purgePersistentCache => boolArgDeprecated('purge-persistent-cache');
bool get disableServiceAuthCodes => boolArgDeprecated('disable-service-auth-codes');
bool get cacheStartupProfile => boolArgDeprecated('cache-startup-profile');
bool get runningWithPrebuiltApplication => argResults![FlutterOptions.kUseApplicationBinary] != null;
bool get trackWidgetCreation => boolArgDeprecated('track-widget-creation');
bool get enableImpeller => boolArgDeprecated('enable-impeller');
bool get uninstallFirst => boolArgDeprecated('uninstall-first');
@override
bool get reportNullSafety => true;
/// Whether to start the application paused by default.
bool get startPausedDefault;
String? get route => stringArgDeprecated('route');
String? get traceAllowlist => stringArgDeprecated('trace-allowlist');
/// Create a debugging options instance for the current `run` or `drive` invocation.
@visibleForTesting
@protected
Future<DebuggingOptions> createDebuggingOptions(bool webMode) async {
final BuildInfo buildInfo = await getBuildInfo();
final int? webBrowserDebugPort = featureFlags.isWebEnabled && argResults!.wasParsed('web-browser-debug-port')
? int.parse(stringArgDeprecated('web-browser-debug-port')!)
: null;
final List<String> webBrowserFlags = featureFlags.isWebEnabled
? stringsArg(FlutterOptions.kWebBrowserFlag)
: const <String>[];
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
buildInfo,
dartEntrypointArgs: stringsArg('dart-entrypoint-args'),
hostname: featureFlags.isWebEnabled ? stringArgDeprecated('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArgDeprecated('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-backend-protocol') == 'sse',
webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-injected-client-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArgDeprecated('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArgDeprecated('web-run-headless'),
webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags,
enableImpeller: enableImpeller,
uninstallFirst: uninstallFirst,
enableDartProfiling: enableDartProfiling,
);
} else {
return DebuggingOptions.enabled(
buildInfo,
startPaused: boolArgDeprecated('start-paused'),
disableServiceAuthCodes: boolArgDeprecated('disable-service-auth-codes'),
cacheStartupProfile: cacheStartupProfile,
enableDds: enableDds,
dartEntrypointArgs: stringsArg('dart-entrypoint-args'),
dartFlags: stringArgDeprecated('dart-flags') ?? '',
useTestFonts: argParser.options.containsKey('use-test-fonts') && boolArgDeprecated('use-test-fonts'),
enableSoftwareRendering: argParser.options.containsKey('enable-software-rendering') && boolArgDeprecated('enable-software-rendering'),
skiaDeterministicRendering: argParser.options.containsKey('skia-deterministic-rendering') && boolArgDeprecated('skia-deterministic-rendering'),
traceSkia: boolArgDeprecated('trace-skia'),
traceAllowlist: traceAllowlist,
traceSkiaAllowlist: stringArgDeprecated('trace-skia-allowlist'),
traceSystrace: boolArgDeprecated('trace-systrace'),
endlessTraceBuffer: boolArgDeprecated('endless-trace-buffer'),
dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation,
cacheSkSL: cacheSkSL,
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
disablePortPublication: await disablePortPublication,
ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
verboseSystemLogs: boolArgDeprecated('verbose-system-logs'),
hostname: featureFlags.isWebEnabled ? stringArgDeprecated('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArgDeprecated('web-port') : '',
webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-protocol') == 'sse',
webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-backend-protocol') == 'sse',
webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArgDeprecated('web-server-debug-injected-client-protocol') == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && boolArgDeprecated('web-allow-expose-url'),
webRunHeadless: featureFlags.isWebEnabled && boolArgDeprecated('web-run-headless'),
webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags,
webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArgDeprecated('web-enable-expression-evaluation'),
webLaunchUrl: featureFlags.isWebEnabled ? stringArgDeprecated('web-launch-url') : null,
vmserviceOutFile: stringArgDeprecated('vmservice-out-file'),
fastStart: argParser.options.containsKey('fast-start')
&& boolArgDeprecated('fast-start')
&& !runningWithPrebuiltApplication,
nullAssertions: boolArgDeprecated('null-assertions'),
nativeNullAssertions: boolArgDeprecated('native-null-assertions'),
enableImpeller: enableImpeller,
uninstallFirst: uninstallFirst,
enableDartProfiling: enableDartProfiling,
);
}
}
}
class RunCommand extends RunCommandBase {
RunCommand({ bool verboseHelp = false }) : super(verboseHelp: verboseHelp) {
requiresPubspecYaml();
usesFilesystemOptions(hide: !verboseHelp);
usesExtraDartFlagOptions(verboseHelp: verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
usesInitializeFromDillOption(hide: !verboseHelp);
// By default, the app should to publish the VM service port over mDNS.
// This will allow subsequent "flutter attach" commands to connect to the VM
// without needing to know the port.
addPublishPort(verboseHelp: verboseHelp);
addMultidexOption();
addIgnoreDeprecationOption();
argParser
..addFlag('await-first-frame-when-tracing',
defaultsTo: true,
help: 'Whether to wait for the first frame when tracing startup ("--trace-startup"), '
'or just dump the trace as soon as the application is running. The first frame '
'is detected by looking for a Timeline event with the name '
'"${Tracing.firstUsefulFrameEventName}". '
"By default, the widgets library's binding takes care of sending this event.",
)
..addFlag('use-test-fonts',
help: 'Enable (and default to) the "Ahem" font. This is a special font '
'used in tests to remove any dependencies on the font metrics. It '
'is enabled when you use "flutter test". Set this flag when running '
'a test using "flutter run" for debugging purposes. This flag is '
'only available when running in debug mode.',
)
..addFlag('build',
defaultsTo: true,
help: 'If necessary, build the app before running.',
)
..addOption('project-root',
hide: !verboseHelp,
help: 'Specify the project root directory.',
)
..addFlag('machine',
hide: !verboseHelp,
negatable: false,
help: 'Handle machine structured JSON command input and provide output '
'and progress in machine friendly format.',
)
..addFlag('hot',
defaultsTo: kHotReloadDefault,
help: 'Run with support for hot reloading. Only available for debug mode. Not available with "--trace-startup".',
)
..addFlag('resident',
defaultsTo: true,
hide: !verboseHelp,
help: 'Stay resident after launching the application. Not available with "--trace-startup".',
)
..addOption('pid-file',
help: 'Specify a file to write the process ID to. '
'You can send SIGUSR1 to trigger a hot reload '
'and SIGUSR2 to trigger a hot restart. '
'The file is created when the signal handlers '
'are hooked and deleted when they are removed.',
)..addFlag(
'report-ready',
help: 'Print "ready" to the console after handling a keyboard command.\n'
'This is primarily useful for tests and other automation, but consider '
'using "--machine" instead.',
hide: !verboseHelp,
)..addFlag('benchmark',
negatable: false,
hide: !verboseHelp,
help: 'Enable a benchmarking mode. This will run the given application, '
'measure the startup time and the app restart time, write the '
'results out to "refresh_benchmark.json", and exit. This flag is '
'intended for use in generating automated flutter benchmarks.',
)
// TODO(zanderso): Off by default with investigating whether this
// is slower for certain use cases.
// See: https://github.com/flutter/flutter/issues/49499
..addFlag('fast-start',
help: 'Whether to quickly bootstrap applications with a minimal app. '
'Currently this is only supported on Android devices. This option '
'cannot be paired with "--${FlutterOptions.kUseApplicationBinary}".',
hide: !verboseHelp,
);
}
@override
final String name = 'run';
@override
DeprecationBehavior get deprecationBehavior => boolArgDeprecated('ignore-deprecation') ? DeprecationBehavior.ignore : _deviceDeprecationBehavior;
DeprecationBehavior _deviceDeprecationBehavior = DeprecationBehavior.none;
@override
final String description = 'Run your Flutter app on an attached device.';
@override
String get category => FlutterCommandCategory.project;
List<Device>? devices;
bool webMode = false;
String? get userIdentifier => stringArgDeprecated(FlutterOptions.kDeviceUser);
@override
bool get startPausedDefault => false;
@override
Future<String?> get usagePath async {
final String? command = await super.usagePath;
if (devices == null) {
return command;
}
if (devices!.length > 1) {
return '$command/all';
}
return '$command/${getNameForTargetPlatform(await devices![0].targetPlatform)}';
}
@override
Future<CustomDimensions> get usageValues async {
String deviceType, deviceOsVersion;
bool isEmulator;
bool anyAndroidDevices = false;
bool anyIOSDevices = false;
bool anyIOSNetworkDevices = false;
if (devices == null || devices!.isEmpty) {
deviceType = 'none';
deviceOsVersion = 'none';
isEmulator = false;
} else if (devices!.length == 1) {
final Device device = devices![0];
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = platform == TargetPlatform.android;
anyIOSDevices = platform == TargetPlatform.ios;
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
deviceType = getNameForTargetPlatform(platform);
deviceOsVersion = await device.sdkNameAndVersion;
isEmulator = await device.isLocalEmulator;
} else {
deviceType = 'multiple';
deviceOsVersion = 'multiple';
isEmulator = false;
for (final Device device in devices!) {
final TargetPlatform platform = await device.targetPlatform;
anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android);
anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios);
if (device is IOSDevice && device.interfaceType == IOSDeviceConnectionInterface.network) {
anyIOSNetworkDevices = true;
}
if (anyAndroidDevices && anyIOSDevices) {
break;
}
}
}
String? iOSInterfaceType;
if (anyIOSDevices) {
iOSInterfaceType = anyIOSNetworkDevices ? 'wireless' : 'usb';
}
String? androidEmbeddingVersion;
final List<String> hostLanguage = <String>[];
if (anyAndroidDevices) {
final AndroidProject androidProject = FlutterProject.current().android;
if (androidProject.existsSync()) {
hostLanguage.add(androidProject.isKotlin ? 'kotlin' : 'java');
androidEmbeddingVersion = androidProject.getEmbeddingVersion().toString().split('.').last;
}
}
if (anyIOSDevices) {
final IosProject iosProject = FlutterProject.current().ios;
if (iosProject.exists) {
final Iterable<File> swiftFiles = iosProject.hostAppRoot
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.where((File file) => globals.fs.path.extension(file.path) == '.swift');
hostLanguage.add(swiftFiles.isNotEmpty ? 'swift' : 'objc');
}
}
final BuildInfo buildInfo = await getBuildInfo();
final String modeName = buildInfo.modeName;
return CustomDimensions(
commandRunIsEmulator: isEmulator,
commandRunTargetName: deviceType,
commandRunTargetOsVersion: deviceOsVersion,
commandRunModeName: modeName,
commandRunProjectModule: FlutterProject.current().isModule,
commandRunProjectHostLanguage: hostLanguage.join(','),
commandRunAndroidEmbeddingVersion: androidEmbeddingVersion,
commandRunEnableImpeller: enableImpeller,
commandRunIOSInterfaceType: iOSInterfaceType,
);
}
@override
bool get shouldRunPub {
// If we are running with a prebuilt application, do not run pub.
if (runningWithPrebuiltApplication) {
return false;
}
return super.shouldRunPub;
}
bool shouldUseHotMode(BuildInfo buildInfo) {
final bool hotArg = boolArgDeprecated('hot');
final bool shouldUseHotMode = hotArg && !traceStartup;
return buildInfo.isDebug && shouldUseHotMode;
}
bool get stayResident => boolArgDeprecated('resident');
bool get awaitFirstFrameWhenTracing => boolArgDeprecated('await-first-frame-when-tracing');
@override
Future<void> validateCommand() async {
// When running with a prebuilt application, no command validation is
// necessary.
if (!runningWithPrebuiltApplication) {
await super.validateCommand();
}
devices = await findAllTargetDevices();
if (devices == null) {
throwToolExit(null);
}
if (globals.deviceManager!.hasSpecifiedAllDevices && runningWithPrebuiltApplication) {
throwToolExit('Using "-d all" with "--${FlutterOptions.kUseApplicationBinary}" is not supported');
}
if (userIdentifier != null
&& devices!.every((Device device) => device.platformType != PlatformType.android)) {
throwToolExit(
'--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.'
);
}
if (devices!.any((Device device) => device is AndroidDevice)) {
_deviceDeprecationBehavior = DeprecationBehavior.exit;
}
// Only support "web mode" with a single web device due to resident runner
// refactoring required otherwise.
webMode = featureFlags.isWebEnabled &&
devices!.length == 1 &&
await devices!.single.targetPlatform == TargetPlatform.web_javascript;
}
@visibleForTesting
Future<ResidentRunner> createRunner({
required bool hotMode,
required List<FlutterDevice> flutterDevices,
required String? applicationBinaryPath,
required FlutterProject flutterProject,
}) async {
if (hotMode && !webMode) {
return HotRunner(
flutterDevices,
target: targetFile,
debuggingOptions: await createDebuggingOptions(webMode),
benchmarkMode: boolArgDeprecated('benchmark'),
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
projectRootPath: stringArgDeprecated('project-root'),
dillOutputPath: stringArgDeprecated('output-dill'),
stayResident: stayResident,
ipv6: ipv6 ?? false,
multidexEnabled: boolArgDeprecated('multidex'),
);
} else if (webMode) {
return webRunnerFactory!.createWebRunner(
flutterDevices.single,
target: targetFile,
flutterProject: flutterProject,
ipv6: ipv6,
debuggingOptions: await createDebuggingOptions(webMode),
stayResident: stayResident,
fileSystem: globals.fs,
usage: globals.flutterUsage,
logger: globals.logger,
systemClock: globals.systemClock,
);
}
return ColdRunner(
flutterDevices,
target: targetFile,
debuggingOptions: await createDebuggingOptions(webMode),
traceStartup: traceStartup,
awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
ipv6: ipv6 ?? false,
stayResident: stayResident,
multidexEnabled: boolArgDeprecated('multidex'),
);
}
@visibleForTesting
Daemon createMachineDaemon() {
final Daemon daemon = Daemon(
DaemonConnection(
daemonStreams: DaemonStreams.fromStdio(globals.stdio, logger: globals.logger),
logger: globals.logger,
),
notifyingLogger: (globals.logger is NotifyingLogger)
? globals.logger as NotifyingLogger
: NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
logToStdout: true,
);
return daemon;
}
@override
Future<FlutterCommandResult> runCommand() async {
// Enable hot mode by default if `--no-hot` was not passed and we are in
// debug mode.
final BuildInfo buildInfo = await getBuildInfo();
final bool hotMode = shouldUseHotMode(buildInfo);
final String? applicationBinaryPath = stringArgDeprecated(FlutterOptions.kUseApplicationBinary);
if (boolArgDeprecated('machine')) {
if (devices!.length > 1) {
throwToolExit('"--machine" does not support "-d all".');
}
final Daemon daemon = createMachineDaemon();
late AppInstance app;
try {
app = await daemon.appDomain.startApp(
devices!.first, globals.fs.currentDirectory.path, targetFile, route,
await createDebuggingOptions(webMode), hotMode,
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
trackWidgetCreation: trackWidgetCreation,
projectRootPath: stringArgDeprecated('project-root'),
packagesFilePath: globalResults!['packages'] as String?,
dillOutputPath: stringArgDeprecated('output-dill'),
ipv6: ipv6 ?? false,
multidexEnabled: boolArgDeprecated('multidex'),
userIdentifier: userIdentifier,
enableDevTools: boolArgDeprecated(FlutterCommand.kEnableDevTools),
);
} on Exception catch (error) {
throwToolExit(error.toString());
}
final DateTime appStartedTime = globals.systemClock.now();
final int result = await app.runner!.waitForAppToFinish();
if (result != 0) {
throwToolExit(null, exitCode: result);
}
return FlutterCommandResult(
ExitStatus.success,
timingLabelParts: <String>['daemon'],
endTimeOverride: appStartedTime,
);
}
globals.terminal.usesTerminalUi = true;
final BuildMode buildMode = getBuildMode();
for (final Device device in devices!) {
if (!await device.supportsRuntimeMode(buildMode)) {
throwToolExit(
'${sentenceCase(getFriendlyModeName(buildMode))} '
'mode is not supported by ${device.name}.',
);
}
if (hotMode) {
if (!device.supportsHotReload) {
throwToolExit('Hot reload is not supported by ${device.name}. Run with "--no-hot".');
}
}
if (await device.isLocalEmulator && await device.supportsHardwareRendering) {
if (boolArgDeprecated('enable-software-rendering')) {
globals.printStatus(
'Using software rendering with device ${device.name}. You may get better performance '
'with hardware mode by configuring hardware rendering for your device.'
);
} else {
globals.printStatus(
'Using hardware rendering with device ${device.name}. If you notice graphics artifacts, '
'consider enabling software rendering with "--enable-software-rendering".'
);
}
}
}
List<String>? expFlags;
if (argParser.options.containsKey(FlutterOptions.kEnableExperiment) &&
stringsArg(FlutterOptions.kEnableExperiment).isNotEmpty) {
expFlags = stringsArg(FlutterOptions.kEnableExperiment);
}
final FlutterProject flutterProject = FlutterProject.current();
final List<FlutterDevice> flutterDevices = <FlutterDevice>[
for (final Device device in devices!)
await FlutterDevice.create(
device,
experimentalFlags: expFlags,
target: targetFile,
buildInfo: buildInfo,
userIdentifier: userIdentifier,
platform: globals.platform,
),
];
final ResidentRunner runner = await createRunner(
applicationBinaryPath: applicationBinaryPath,
flutterDevices: flutterDevices,
flutterProject: flutterProject,
hotMode: hotMode,
);
DateTime? appStartedTime;
// Sync completer so the completing agent attaching to the resident doesn't
// need to know about analytics.
//
// Do not add more operations to the future.
final Completer<void> appStartedTimeRecorder = Completer<void>.sync();
TerminalHandler? handler;
// This callback can't throw.
unawaited(appStartedTimeRecorder.future.then<void>(
(_) {
appStartedTime = globals.systemClock.now();
if (stayResident) {
handler = TerminalHandler(
runner,
logger: globals.logger,
terminal: globals.terminal,
signals: globals.signals,
processInfo: globals.processInfo,
reportReady: boolArgDeprecated('report-ready'),
pidFile: stringArgDeprecated('pid-file'),
)
..registerSignalHandlers()
..setupTerminal();
}
}
));
try {
final int? result = await runner.run(
appStartedCompleter: appStartedTimeRecorder,
enableDevTools: stayResident && boolArgDeprecated(FlutterCommand.kEnableDevTools),
route: route,
);
handler?.stop();
if (result != 0) {
throwToolExit(null, exitCode: result);
}
} on RPCError catch (error) {
if (error.code == RPCErrorCodes.kServiceDisappeared) {
throwToolExit('Lost connection to device.');
}
rethrow;
} finally {
// However we exited from the runner, ensure the terminal has line mode
// and echo mode enabled before we return the user to the shell.
try {
globals.terminal.singleCharMode = false;
} on StdinException {
// Do nothing, if the STDIN handle is no longer available, there is nothing actionable for us to do at this point
}
}
return FlutterCommandResult(
ExitStatus.success,
timingLabelParts: <String?>[
if (hotMode) 'hot' else 'cold',
getModeName(getBuildMode()),
if (devices!.length == 1)
getNameForTargetPlatform(await devices![0].targetPlatform)
else
'multiple',
if (devices!.length == 1 && await devices![0].isLocalEmulator)
'emulator'
else
null,
],
endTimeOverride: appStartedTime,
);
}
}