blob: 3f5aee21bbb982df562d345ccb7afbffbfd3a07c [file]
// 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:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart';
import '../android/android_device.dart';
import '../android/android_workflow.dart' as android_workflow;
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../build_info.dart';
import '../device.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../hook_runner.dart' show hookRunner;
import '../ios/devices.dart';
import '../project.dart';
import '../resident_runner.dart';
import '../run_cold.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../tracing.dart';
import '../web/compile.dart';
import '../web/devfs_config.dart';
import '../web/web_options.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({super.toolContext, required super.verboseHelp}) {
addBuildModeFlags(verboseHelp: verboseHelp, defaultToRelease: false);
usesDartDefineOption();
usesWebDefineOption();
usesFlavorOption();
usesWebResourcesCdnFlag();
addNativeNullAssertions(hide: !verboseHelp);
usesApplicationBinaryOption();
argParser.addDescriptors(<OptionDescriptor<Object?>>[
DebuggingOptionDescriptors.traceStartup,
DebuggingOptionDescriptors.cacheStartupProfile,
DebuggingOptionDescriptors.verboseSystemLogs,
DebuggingOptionDescriptors.purgePersistentCache,
DebuggingOptionDescriptors.route,
DebuggingOptionDescriptors.vmserviceOutFile,
DebuggingOptionDescriptors.disableServiceAuthCodes,
DebuggingOptionDescriptors.disableServiceOriginCheck,
DebuggingOptionDescriptors.startPaused(defaultsTo: startPausedDefault),
DebuggingOptionDescriptors.dartFlags,
DebuggingOptionDescriptors.endlessTraceBuffer,
DebuggingOptionDescriptors.traceSystrace,
DebuggingOptionDescriptors.traceToFile,
DebuggingOptionDescriptors.profileMicrotasks,
DebuggingOptionDescriptors.traceSkia,
DebuggingOptionDescriptors.traceAllowlist,
DebuggingOptionDescriptors.traceSkiaAllowlist,
DebuggingOptionDescriptors.enableDartProfiling,
DebuggingOptionDescriptors.profileStartup,
DebuggingOptionDescriptors.enableSoftwareRendering,
DebuggingOptionDescriptors.skiaDeterministicRendering,
DebuggingOptionDescriptors.dartEntrypointArgs,
DebuggingOptionDescriptors.uninstallFirst,
WebOptions.wasm,
DebuggingOptionDescriptors.iosProfileDebugger,
], verboseHelp: verboseHelp);
usesWebOptions(verboseHelp: verboseHelp);
usesTargetOption();
usesPortOptions(verboseHelp: verboseHelp);
usesIpv6Flag(verboseHelp: verboseHelp);
usesPubOption();
usesTrackWidgetCreation(verboseHelp: verboseHelp);
usesDeviceUserOption();
usesDeviceTimeoutOption();
usesDeviceConnectionOption();
addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions(verboseHelp: verboseHelp);
addAndroidSpecificBuildOptions(hide: !verboseHelp);
usesFatalWarningsOption(verboseHelp: verboseHelp);
addEnableImpellerFlag(verboseHelp: verboseHelp);
addEnableFlutterGpuFlag(verboseHelp: verboseHelp);
addEnableVulkanValidationFlag(verboseHelp: verboseHelp);
addEnableEmbedderApiFlag(verboseHelp: verboseHelp);
addEnableHcppFlag(verboseHelp: verboseHelp);
addTestFlag(verboseHelp: verboseHelp);
usesAdbLogFilteringOption(hide: !verboseHelp);
}
bool get traceStartup => getValue(DebuggingOptionDescriptors.traceStartup);
bool get traceSystrace => getValue(DebuggingOptionDescriptors.traceSystrace);
bool get enableDartProfiling => getValue(DebuggingOptionDescriptors.enableDartProfiling);
bool get purgePersistentCache => getValue(DebuggingOptionDescriptors.purgePersistentCache);
bool get disableServiceAuthCodes => getValue(DebuggingOptionDescriptors.disableServiceAuthCodes);
bool get disableServiceOriginCheck =>
getValue(DebuggingOptionDescriptors.disableServiceOriginCheck);
bool get cacheStartupProfile => getValue(DebuggingOptionDescriptors.cacheStartupProfile);
bool get runningWithPrebuiltApplication => prebuiltApplicationBinaryPath != null;
String? get prebuiltApplicationBinaryPath => stringArg(FlutterOptions.kUseApplicationBinary);
bool get trackWidgetCreation => boolArg('track-widget-creation');
ImpellerStatus get enableImpeller =>
ImpellerStatus.fromBool(getValue(DebuggingOptionDescriptors.enableImpeller));
bool get enableFlutterGpu => getValue(DebuggingOptionDescriptors.enableFlutterGpu) ?? false;
bool get enableVulkanValidation => getValue(DebuggingOptionDescriptors.enableVulkanValidation);
bool get uninstallFirst => getValue(DebuggingOptionDescriptors.uninstallFirst);
bool get enableEmbedderApi => getValue(DebuggingOptionDescriptors.enableEmbedderApi);
bool get testFlag => getValue(DebuggingOptionDescriptors.testFlag);
@override
bool get refreshWirelessDevices => true;
/// Whether to start the application paused by default.
bool get startPausedDefault;
String? get route => getValue(DebuggingOptionDescriptors.route);
String? get traceAllowlist => getValue(DebuggingOptionDescriptors.traceAllowlist);
bool get useWasm => getValue(WebOptions.wasm);
// Keep in sync with the [TestCommand.webRenderer] getter.
WebRendererMode get webRenderer {
final List<String> dartDefines = extractDartDefines(
defineConfigJsonMap: extractDartDefineConfigJsonMap(),
);
return WebRendererMode.fromDartDefines(dartDefines, useWasm: useWasm);
}
/// Create a debugging options instance for the current `run` or `drive` invocation.
@visibleForTesting
@protected
Future<DebuggingOptions> createDebuggingOptions({WebDevServerConfig? webDevServerConfig}) async {
final BuildInfo buildInfo = await getBuildInfo();
final int? webBrowserDebugPort = featureFlags.isWebEnabled
? getValue(WebOptions.webBrowserDebugPort)
: null;
final List<String> webBrowserFlags = featureFlags.isWebEnabled
? getValue(WebOptions.webBrowserFlags)
: const <String>[];
final bool? webCrossOriginIsolation = wasParsed(WebOptions.crossOriginIsolation)
? getValue(WebOptions.crossOriginIsolation)
: null;
final bool? iosProfileDebugger = wasParsed(DebuggingOptionDescriptors.iosProfileDebugger)
? getValue(DebuggingOptionDescriptors.iosProfileDebugger)
: null;
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
buildInfo,
dartEntrypointArgs: getValue(DebuggingOptionDescriptors.dartEntrypointArgs),
webUseSseForDebugProxy:
featureFlags.isWebEnabled && getValue(WebOptions.webServerDebugProtocol) == 'sse',
webUseSseForDebugBackend:
featureFlags.isWebEnabled &&
getValue(WebOptions.webServerDebugBackendProtocol) == 'sse',
webUseSseForInjectedClient:
featureFlags.isWebEnabled &&
getValue(WebOptions.webServerDebugInjectedClientProtocol) == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && getValue(WebOptions.webAllowExposeUrl),
webRunHeadless: featureFlags.isWebEnabled && getValue(WebOptions.webRunHeadless),
webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags,
webCrossOriginIsolation: webCrossOriginIsolation,
webRenderer: webRenderer,
webUseWasm: useWasm,
enableImpeller: enableImpeller,
enableFlutterGpu: enableFlutterGpu,
enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst,
enableDartProfiling: enableDartProfiling,
enableEmbedderApi: enableEmbedderApi,
usingCISystem: usingCISystem,
debugLogsDirectoryPath: debugLogsDirectoryPath,
webDevServerConfig: webDevServerConfig,
enableHcpp: explicitEnableHcpp,
testFlag: testFlag,
iosProfileDebugger: iosProfileDebugger,
traceSystrace: traceSystrace,
);
} else {
return DebuggingOptions.enabled(
buildInfo,
startPaused: getValue(DebuggingOptionDescriptors.startPausedOption),
disableServiceAuthCodes: disableServiceAuthCodes,
disableServiceOriginCheck: disableServiceOriginCheck,
cacheStartupProfile: cacheStartupProfile,
enableDds: enableDds,
adbLogFiltering:
hasOption(DebuggingOptionDescriptors.adbLogFiltering) &&
getValue(DebuggingOptionDescriptors.adbLogFiltering),
dartEntrypointArgs: getValue(DebuggingOptionDescriptors.dartEntrypointArgs),
dartFlags: getValue(DebuggingOptionDescriptors.dartFlags) ?? '',
useTestFonts:
hasOption(DebuggingOptionDescriptors.useTestFonts) &&
getValue(DebuggingOptionDescriptors.useTestFonts),
enableSoftwareRendering:
hasOption(DebuggingOptionDescriptors.enableSoftwareRendering) &&
getValue(DebuggingOptionDescriptors.enableSoftwareRendering),
skiaDeterministicRendering:
hasOption(DebuggingOptionDescriptors.skiaDeterministicRendering) &&
getValue(DebuggingOptionDescriptors.skiaDeterministicRendering),
traceSkia: getValue(DebuggingOptionDescriptors.traceSkia),
traceAllowlist: traceAllowlist,
traceSkiaAllowlist: getValue(DebuggingOptionDescriptors.traceSkiaAllowlist),
traceSystrace: traceSystrace,
traceToFile: getValue(DebuggingOptionDescriptors.traceToFile),
endlessTraceBuffer: getValue(DebuggingOptionDescriptors.endlessTraceBuffer),
profileMicrotasks: getValue(DebuggingOptionDescriptors.profileMicrotasks),
purgePersistentCache: purgePersistentCache,
deviceVmServicePort: deviceVmservicePort,
hostVmServicePort: hostVmservicePort,
disablePortPublication: await disablePortPublication,
ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
verboseSystemLogs: getValue(DebuggingOptionDescriptors.verboseSystemLogs),
webUseSseForDebugProxy:
featureFlags.isWebEnabled && getValue(WebOptions.webServerDebugProtocol) == 'sse',
webUseSseForDebugBackend:
featureFlags.isWebEnabled &&
getValue(WebOptions.webServerDebugBackendProtocol) == 'sse',
webUseSseForInjectedClient:
featureFlags.isWebEnabled &&
getValue(WebOptions.webServerDebugInjectedClientProtocol) == 'sse',
webEnableExposeUrl: featureFlags.isWebEnabled && getValue(WebOptions.webAllowExposeUrl),
webRunHeadless: featureFlags.isWebEnabled && getValue(WebOptions.webRunHeadless),
webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags,
webEnableExpressionEvaluation:
featureFlags.isWebEnabled && getValue(WebOptions.webEnableExpressionEvaluation),
webLaunchUrl: featureFlags.isWebEnabled ? getValue(WebOptions.webLaunchUrl) : null,
webCrossOriginIsolation: webCrossOriginIsolation,
webRenderer: webRenderer,
webUseWasm: useWasm,
vmserviceOutFile: getValue(DebuggingOptionDescriptors.vmserviceOutFile),
nativeNullAssertions: getValue(CommonOptions.nativeNullAssertions),
enableImpeller: enableImpeller,
enableFlutterGpu: enableFlutterGpu,
enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst,
enableDartProfiling: enableDartProfiling,
profileStartup: getValue(DebuggingOptionDescriptors.profileStartup),
enableEmbedderApi: enableEmbedderApi,
usingCISystem: usingCISystem,
debugLogsDirectoryPath: debugLogsDirectoryPath,
enableDevTools: getValue(DebuggingOptionDescriptors.enableDevTools),
ipv6: getValue(DebuggingOptionDescriptors.ipv6),
printDtd: boolArg(FlutterGlobalOptions.kPrintDtd, global: true),
enableHcpp: explicitEnableHcpp,
webDevServerConfig: webDevServerConfig,
testFlag: testFlag,
iosProfileDebugger: iosProfileDebugger,
);
}
}
Future<WebDevServerConfig> webDevServerConfigCore() async {
final WebDevServerConfig fileConfig = await WebDevServerConfig.loadFromFile(
fileSystem: globals.fs,
logger: globals.logger,
);
final int? webPort = getValue(WebOptions.webPort);
// Determine HTTPS config with CLI > file precedence
final HttpsConfig? httpsConfig = HttpsConfig.parse(
getValue(WebOptions.webTlsCertPath) ?? fileConfig.https?.certPath,
getValue(WebOptions.webTlsCertKeyPath) ?? fileConfig.https?.certKeyPath,
);
final String? baseHref = getValue(WebOptions.baseHref) ?? fileConfig.baseHref;
if (baseHref != null && !(baseHref.startsWith('/') && baseHref.endsWith('/'))) {
throwToolExit(
'Received a --base-href value of "$baseHref"\n'
'--base-href should start and end with /',
);
}
final WebDevServerConfig webDevServerConfig = fileConfig.copyWith(
host: getValue(WebOptions.webHostname),
port: webPort,
https: httpsConfig,
headers: extractWebHeaders(),
baseHref: baseHref,
);
return webDevServerConfig;
}
}
class RunCommand extends RunCommandBase {
RunCommand({super.toolContext, bool verboseHelp = false}) : super(verboseHelp: verboseHelp) {
requiresPubspecYaml();
usesFilesystemOptions(hide: !verboseHelp);
usesExtraDartFlagOptions(verboseHelp: verboseHelp);
usesFrontendServerStarterPathOption(verboseHelp: verboseHelp);
addEnableExperimentation(hide: !verboseHelp);
usesInitializeFromDillOption(hide: !verboseHelp);
usesNativeAssetsOption(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);
addIgnoreDeprecationOption();
addMachineOutputFlag(verboseHelp: verboseHelp);
argParser.addDescriptor(DebuggingOptionDescriptors.useTestFonts);
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(
'build',
defaultsTo: true,
hide: !verboseHelp,
help:
'(deprecated) If necessary, build the app before running. To use an existing app, pass the "--${FlutterOptions.kUseApplicationBinary}" '
'flag with an existing application artifact.',
)
..addOption('project-root', hide: !verboseHelp, help: 'Specify the project root directory.')
..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.',
);
}
@override
final name = 'run';
@override
DeprecationBehavior get deprecationBehavior =>
boolArg('ignore-deprecation') ? DeprecationBehavior.ignore : _deviceDeprecationBehavior;
DeprecationBehavior _deviceDeprecationBehavior = DeprecationBehavior.none;
@override
final description = 'Run your Flutter app on an attached device.';
@override
String get category => FlutterCommandCategory.project;
List<Device>? devices;
Future<WebDevServerConfig?> getWebDevServerConfig() async {
// Only support "web mode" with a single web device due to resident runner
// refactoring required otherwise.
if (featureFlags.isWebEnabled &&
devices != null &&
devices!.length == 1 &&
await devices!.single.targetPlatform == TargetPlatform.web_javascript) {
final WebDevServerConfig webDevServerConfig = await webDevServerConfigCore();
return webDevServerConfig;
}
return null;
}
String? get userIdentifier => stringArg(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/${(await devices![0].targetPlatform).getName()}';
}
@override
Future<analytics.Event> unifiedAnalyticsUsageValues(String commandPath) async {
final AnalyticsUsageValuesRecord record = await _sharedAnalyticsUsageValues;
return analytics.Event.commandUsageValues(
workflow: commandPath,
commandHasTerminal: hasTerminal,
runIsEmulator: record.runIsEmulator,
runTargetName: record.runTargetName,
runTargetOsVersion: record.runTargetOsVersion,
runModeName: record.runModeName,
runProjectModule: record.runProjectModule,
runProjectHostLanguage: record.runProjectHostLanguage,
runAndroidEmbeddingVersion: record.runAndroidEmbeddingVersion,
runEnableImpeller: record.runEnableImpeller,
runIOSInterfaceType: record.runIOSInterfaceType,
runIsTest: record.runIsTest,
runEnableHcpp: record.runEnableHcpp,
);
}
late final Future<AnalyticsUsageValuesRecord> _sharedAnalyticsUsageValues = (() async {
String deviceType, deviceOsVersion;
bool isEmulator;
var anyAndroidDevices = false;
var anyIOSDevices = false;
var anyWirelessIOSDevices = 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.isWirelesslyConnected) {
anyWirelessIOSDevices = true;
}
deviceType = platform.getName();
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.isWirelesslyConnected) {
anyWirelessIOSDevices = true;
}
if (anyAndroidDevices && anyIOSDevices) {
break;
}
}
}
String? iOSInterfaceType;
if (anyIOSDevices) {
iOSInterfaceType = anyWirelessIOSDevices ? 'wireless' : 'usb';
}
String? androidEmbeddingVersion;
final 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 (
runIsEmulator: isEmulator,
runTargetName: deviceType,
runTargetOsVersion: deviceOsVersion,
runModeName: modeName,
runProjectModule: project.isModule,
runProjectHostLanguage: hostLanguage.join(','),
runAndroidEmbeddingVersion: androidEmbeddingVersion,
runEnableImpeller: enableImpeller.asBool,
runIOSInterfaceType: iOSInterfaceType,
runIsTest: targetFile.endsWith('_test.dart'),
// Best-effort estimate from the main manifest; does not account for build-type
// or flavor overlay manifests (e.g. EnableHcpp set only in src/debug/).
runEnableHcpp: anyAndroidDevices && project.android.existsSync()
? (explicitEnableHcpp ?? project.android.computeHcppEnabled(ifAbsent: enableHcpp))
: null,
);
})();
@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 = boolArg('hot');
final bool shouldUseHotMode = hotArg && !traceStartup;
return buildInfo.isDebug && shouldUseHotMode;
}
bool get stayResident => boolArg('resident');
bool get awaitFirstFrameWhenTracing => boolArg('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);
}
final WebDevServerConfig? webDevServerConfig = await getWebDevServerConfig();
final webMode = webDevServerConfig != 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;
}
if (useWasm && !webMode) {
throwToolExit('--wasm is only supported on the web platform');
}
if (webRenderer == WebRendererMode.skwasm && !useWasm) {
throwToolExit('Skwasm renderer requires --wasm');
}
final String? flavor = stringArg('flavor');
final bool flavorsSupportedOnEveryDevice = devices!.every(
(Device device) => device.supportsFlavors,
);
if (flavor != null && !flavorsSupportedOnEveryDevice) {
globals.printWarning(
'--flavor is only supported for Android, Linux, macOS, iOS, and Windows devices. '
'Flavor-related features may not function properly and could '
'behave differently in a future release.',
);
}
if (argResults!.wasParsed('build')) {
if (boolArg('build')) {
globals.printWarning(
'The "--build" flag is deprecated and will be removed in a future release. '
'Building is the default behavior, so this flag can be safely removed.',
);
} else {
globals.printWarning(
'The "--no-build" flag is deprecated and will be removed in a future release. '
'To use a prebuilt application, pass "--${FlutterOptions.kUseApplicationBinary}".',
);
}
}
}
@visibleForTesting
Future<ResidentRunner> createRunner({
required bool hotMode,
required List<FlutterDevice> flutterDevices,
required String? applicationBinaryPath,
required FlutterProject flutterProject,
}) async {
final WebDevServerConfig? webDevServerConfig = await getWebDevServerConfig();
final webMode = webDevServerConfig != null;
final DebuggingOptions debuggingOptions = await createDebuggingOptions(
webDevServerConfig: webDevServerConfig,
);
if (hotMode && !webMode) {
return HotRunner(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
benchmarkMode: boolArg('benchmark'),
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
projectRootPath: stringArg('project-root'),
dillOutputPath: stringArg('output-dill'),
stayResident: stayResident,
analytics: globals.analytics,
nativeAssetsYamlFile: stringArg(FlutterOptions.kNativeAssetsYamlFile),
dartBuilder: hookRunner,
logger: globals.logger,
);
} else if (webMode) {
return webRunnerFactory!.createWebRunner(
flutterDevices.single,
target: targetFile,
flutterProject: flutterProject,
debuggingOptions: debuggingOptions,
stayResident: stayResident,
fileSystem: globals.fs,
analytics: globals.analytics,
logger: globals.logger,
terminal: globals.terminal,
platform: globals.platform,
outputPreferences: globals.outputPreferences,
systemClock: globals.systemClock,
webDefines: extractWebDefines(),
);
}
return ColdRunner(
flutterDevices,
target: targetFile,
debuggingOptions: debuggingOptions,
traceStartup: traceStartup,
awaitFirstFrameWhenTracing: awaitFirstFrameWhenTracing,
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
stayResident: stayResident,
dartBuilder: hookRunner,
);
}
@visibleForTesting
Daemon createMachineDaemon() {
return Daemon.createMachineDaemon(
analytics: globals.analytics,
androidSdk: globals.androidSdk,
androidWorkflow: android_workflow.androidWorkflow,
deviceManager: globals.deviceManager,
featureFlags: featureFlags,
fileSystem: globals.fs,
java: globals.java,
logger: globals.logger,
outputPreferences: globals.outputPreferences,
platform: globals.platform,
processManager: globals.processManager,
stdio: globals.stdio,
systemClock: globals.systemClock,
terminal: globals.terminal,
toolContext: toolContext!,
);
}
@override
Future<FlutterCommandResult> runCommand() async {
final BuildInfo buildInfo = await getBuildInfo();
// Enable hot mode by default if `--no-hot` was not passed and we are in
// debug mode.
final bool hotMode = shouldUseHotMode(buildInfo);
final String? applicationBinaryPath = prebuiltApplicationBinaryPath;
final WebDevServerConfig? webDevServerConfig = await getWebDevServerConfig();
if (outputMachineFormat) {
if (devices!.length > 1) {
throwToolExit('"--machine" does not support "-d all".');
}
final Daemon daemon = createMachineDaemon();
late AppInstance app;
final DebuggingOptions debuggingOptions = await createDebuggingOptions(
webDevServerConfig: webDevServerConfig,
);
try {
app = await daemon.appDomain.startApp(
devices!.first,
globals.fs.currentDirectory.path,
targetFile,
route,
debuggingOptions,
hotMode,
webDefines: extractWebDefines(),
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
trackWidgetCreation: trackWidgetCreation,
projectRootPath: stringArg('project-root'),
packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?,
dillOutputPath: stringArg('output-dill'),
userIdentifier: userIdentifier,
);
} 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(
'${buildMode.uppercaseFriendlyName}'
'mode is not supported by ${device.displayName}.',
);
}
if (hotMode) {
if (!device.supportsHotReload) {
throwToolExit(
'Hot reload is not supported by ${device.displayName}. '
'Run with "--no-hot".',
);
}
}
}
final flutterDevices = <FlutterDevice>[
for (final Device device in devices!)
await FlutterDevice.create(
device,
toolContext: toolContext!,
buildInfo: buildInfo,
target: targetFile,
userIdentifier: userIdentifier,
),
];
final ResidentRunner runner = await createRunner(
applicationBinaryPath: applicationBinaryPath,
flutterDevices: flutterDevices,
flutterProject: project,
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 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: boolArg('report-ready'),
pidFile: stringArg('pid-file'),
)
..registerSignalHandlers()
..setupTerminal();
}
}),
);
try {
final int result = await runner.run(
appStartedCompleter: appStartedTimeRecorder,
route: route,
);
handler?.stop();
if (result != 0) {
throwToolExit(null, exitCode: result);
}
} on RPCError catch (error) {
if (error.code == RPCErrorKind.kServiceDisappeared.code ||
error.code == RPCErrorKind.kConnectionDisposed.code ||
error.message.contains('Service connection disposed')) {
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',
getBuildMode().cliName,
if (devices!.length == 1) (await devices![0].targetPlatform).getName() else 'multiple',
if (devices!.length == 1 && await devices![0].isLocalEmulator) 'emulator' else null,
],
endTimeOverride: appStartedTime,
);
}
}
/// Schema for the usage values to send for analytics reporting.
typedef AnalyticsUsageValuesRecord = ({
String? runAndroidEmbeddingVersion,
bool? runEnableImpeller,
String? runIOSInterfaceType,
bool runIsEmulator,
bool runIsTest,
String runModeName,
String runProjectHostLanguage,
bool runProjectModule,
String runTargetName,
String runTargetOsVersion,
bool? runEnableHcpp,
});