| // 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. |
| |
| // @dart = 2.8 |
| |
| 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/utils.dart'; |
| import '../build_info.dart'; |
| import '../device.dart'; |
| import '../features.dart'; |
| import '../globals.dart' as globals; |
| 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); |
| 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('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!)' |
| ) |
| ..addOption('use-application-binary', |
| help: 'Specify a pre-built application binary to use when running. For Android applications, ' |
| 'this must be the path to an APK. For iOS applications, the path to an IPA. Other device types ' |
| 'do not yet support prebuilt application binaries.', |
| valueHelp: 'path/to/app.apk', |
| ) |
| ..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 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', |
| ) |
| ..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.' |
| ); |
| 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); |
| } |
| |
| bool get traceStartup => boolArg('trace-startup'); |
| bool get cacheSkSL => boolArg('cache-sksl'); |
| bool get dumpSkpOnShaderCompilation => boolArg('dump-skp-on-shader-compilation'); |
| bool get purgePersistentCache => boolArg('purge-persistent-cache'); |
| bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes'); |
| bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null; |
| bool get trackWidgetCreation => boolArg('track-widget-creation'); |
| |
| @override |
| bool get reportNullSafety => true; |
| |
| /// Whether to start the application paused by default. |
| bool get startPausedDefault; |
| |
| String get route => stringArg('route'); |
| |
| String get traceAllowlist => stringArg('trace-allowlist'); |
| |
| /// Create a debugging options instance for the current `run` or `drive` invocation. |
| Future<DebuggingOptions> createDebuggingOptions(bool webMode) async { |
| final BuildInfo buildInfo = await getBuildInfo(); |
| final int browserDebugPort = featureFlags.isWebEnabled && argResults.wasParsed('web-browser-debug-port') |
| ? int.parse(stringArg('web-browser-debug-port')) |
| : null; |
| if (buildInfo.mode.isRelease) { |
| return DebuggingOptions.disabled( |
| buildInfo, |
| dartEntrypointArgs: stringsArg('dart-entrypoint-args'), |
| hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', |
| port: featureFlags.isWebEnabled ? stringArg('web-port') : '', |
| webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', |
| webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', |
| webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse', |
| webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), |
| webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), |
| webBrowserDebugPort: browserDebugPort, |
| ); |
| } else { |
| return DebuggingOptions.enabled( |
| buildInfo, |
| startPaused: boolArg('start-paused'), |
| disableServiceAuthCodes: boolArg('disable-service-auth-codes'), |
| enableDds: enableDds, |
| dartEntrypointArgs: stringsArg('dart-entrypoint-args'), |
| dartFlags: stringArg('dart-flags') ?? '', |
| useTestFonts: argParser.options.containsKey('use-test-fonts') && boolArg('use-test-fonts'), |
| enableSoftwareRendering: argParser.options.containsKey('enable-software-rendering') && boolArg('enable-software-rendering'), |
| skiaDeterministicRendering: argParser.options.containsKey('skia-deterministic-rendering') && boolArg('skia-deterministic-rendering'), |
| traceSkia: boolArg('trace-skia'), |
| traceAllowlist: traceAllowlist, |
| traceSystrace: boolArg('trace-systrace'), |
| endlessTraceBuffer: boolArg('endless-trace-buffer'), |
| dumpSkpOnShaderCompilation: dumpSkpOnShaderCompilation, |
| cacheSkSL: cacheSkSL, |
| purgePersistentCache: purgePersistentCache, |
| deviceVmServicePort: deviceVmservicePort, |
| hostVmServicePort: hostVmservicePort, |
| disablePortPublication: disablePortPublication, |
| ddsPort: ddsPort, |
| devToolsServerAddress: devToolsServerAddress, |
| verboseSystemLogs: boolArg('verbose-system-logs'), |
| hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', |
| port: featureFlags.isWebEnabled ? stringArg('web-port') : '', |
| webUseSseForDebugProxy: featureFlags.isWebEnabled && stringArg('web-server-debug-protocol') == 'sse', |
| webUseSseForDebugBackend: featureFlags.isWebEnabled && stringArg('web-server-debug-backend-protocol') == 'sse', |
| webUseSseForInjectedClient: featureFlags.isWebEnabled && stringArg('web-server-debug-injected-client-protocol') == 'sse', |
| webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), |
| webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), |
| webBrowserDebugPort: browserDebugPort, |
| webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'), |
| vmserviceOutFile: stringArg('vmservice-out-file'), |
| fastStart: argParser.options.containsKey('fast-start') |
| && boolArg('fast-start') |
| && !runningWithPrebuiltApplication, |
| nullAssertions: boolArg('null-assertions'), |
| nativeNullAssertions: boolArg('native-null-assertions'), |
| ); |
| } |
| } |
| } |
| |
| class RunCommand extends RunCommandBase { |
| RunCommand({ bool verboseHelp = false }) : super(verboseHelp: verboseHelp) { |
| requiresPubspecYaml(); |
| usesFilesystemOptions(hide: !verboseHelp); |
| usesExtraDartFlagOptions(verboseHelp: verboseHelp); |
| addEnableExperimentation(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(enabledByDefault: true, verboseHelp: verboseHelp); |
| argParser |
| ..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).', |
| ) |
| ..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', |
| negatable: true, |
| 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', |
| negatable: true, |
| defaultsTo: kHotReloadDefault, |
| help: 'Run with support for hot reloading. Only available for debug mode. Not available with "--trace-startup".', |
| ) |
| ..addFlag('resident', |
| negatable: true, |
| 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(jonahwilliams): Off by default with investigating whether this |
| // is slower for certain use cases. |
| // See: https://github.com/flutter/flutter/issues/49499 |
| ..addFlag('fast-start', |
| negatable: true, |
| defaultsTo: false, |
| help: 'Whether to quickly bootstrap applications with a minimal app. ' |
| 'Currently this is only supported on Android devices. This option ' |
| 'cannot be paired with "--use-application-binary".', |
| hide: !verboseHelp, |
| ); |
| } |
| |
| @override |
| final String name = 'run'; |
| |
| @override |
| final String description = 'Run your Flutter app on an attached device.'; |
| |
| List<Device> devices; |
| bool webMode = false; |
| |
| 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/${getNameForTargetPlatform(await devices[0].targetPlatform)}'; |
| } |
| |
| @override |
| Future<CustomDimensions> get usageValues async { |
| String deviceType, deviceOsVersion; |
| bool isEmulator; |
| bool anyAndroidDevices = false; |
| bool anyIOSDevices = false; |
| |
| if (devices == null || devices.isEmpty) { |
| deviceType = 'none'; |
| deviceOsVersion = 'none'; |
| isEmulator = false; |
| } else if (devices.length == 1) { |
| final TargetPlatform platform = await devices[0].targetPlatform; |
| anyAndroidDevices = platform == TargetPlatform.android; |
| anyIOSDevices = platform == TargetPlatform.ios; |
| deviceType = getNameForTargetPlatform(platform); |
| deviceOsVersion = await devices[0].sdkNameAndVersion; |
| isEmulator = await devices[0].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 (anyAndroidDevices && anyIOSDevices) { |
| break; |
| } |
| } |
| } |
| |
| String androidEmbeddingVersion; |
| final List<String> hostLanguage = <String>[]; |
| if (anyAndroidDevices) { |
| final AndroidProject androidProject = FlutterProject.current().android; |
| if (androidProject != null && androidProject.existsSync()) { |
| hostLanguage.add(androidProject.isKotlin ? 'kotlin' : 'java'); |
| androidEmbeddingVersion = androidProject.getEmbeddingVersion().toString().split('.').last; |
| } |
| } |
| if (anyIOSDevices) { |
| final IosProject iosProject = FlutterProject.current().ios; |
| if (iosProject != null && 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, |
| ); |
| } |
| |
| @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') ?? false; |
| 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); |
| } |
| if (globals.deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) { |
| throwToolExit('Using "-d all" with "--use-application-binary" is not supported'); |
| } |
| |
| if (userIdentifier != null |
| && devices.every((Device device) => device is! AndroidDevice)) { |
| throwToolExit( |
| '--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.' |
| ); |
| } |
| // 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: boolArg('benchmark'), |
| applicationBinary: applicationBinaryPath == null |
| ? null |
| : globals.fs.file(applicationBinaryPath), |
| projectRootPath: stringArg('project-root'), |
| dillOutputPath: stringArg('output-dill'), |
| stayResident: stayResident, |
| ipv6: ipv6, |
| ); |
| } else if (webMode) { |
| return webRunnerFactory.createWebRunner( |
| flutterDevices.single, |
| target: targetFile, |
| flutterProject: flutterProject, |
| ipv6: ipv6, |
| debuggingOptions: await createDebuggingOptions(webMode), |
| stayResident: stayResident, |
| urlTunneller: null, |
| 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, |
| stayResident: stayResident, |
| ); |
| } |
| |
| @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 = stringArg('use-application-binary'); |
| |
| if (boolArg('machine')) { |
| if (devices.length > 1) { |
| throwToolExit('"--machine" does not support "-d all".'); |
| } |
| final Daemon daemon = Daemon( |
| stdinCommandStream, |
| stdoutCommandResponse, |
| notifyingLogger: (globals.logger is NotifyingLogger) |
| ? globals.logger as NotifyingLogger |
| : NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger), |
| logToStdout: true, |
| ); |
| 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: stringArg('project-root'), |
| packagesFilePath: globalResults['packages'] as String, |
| dillOutputPath: stringArg('output-dill'), |
| ipv6: ipv6, |
| machine: true, |
| ); |
| } 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( |
| '${toTitleCase(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 (boolArg('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, |
| fileSystemRoots: stringsArg(FlutterOptions.kFileSystemRoot), |
| fileSystemScheme: stringArg(FlutterOptions.kFileSystemScheme), |
| 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: boolArg('report-ready'), |
| pidFile: stringArg('pid-file'), |
| ) |
| ..registerSignalHandlers() |
| ..setupTerminal(); |
| } |
| } |
| )); |
| try { |
| final int result = await runner.run( |
| appStartedCompleter: appStartedTimeRecorder, |
| enableDevTools: stayResident && boolArg(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; |
| } |
| 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, |
| ); |
| } |
| } |