| // Copyright 2015 The Chromium 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 '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/utils.dart'; |
| import '../build_info.dart'; |
| import '../cache.dart'; |
| import '../device.dart'; |
| import '../globals.dart'; |
| import '../ios/mac.dart'; |
| import '../resident_runner.dart'; |
| import '../run_cold.dart'; |
| import '../run_hot.dart'; |
| import '../runner/flutter_command.dart'; |
| import 'daemon.dart'; |
| |
| abstract class RunCommandBase extends FlutterCommand { |
| // Used by run and drive commands. |
| RunCommandBase() { |
| addBuildModeFlags(defaultToRelease: false); |
| usesFlavorOption(); |
| argParser.addFlag('trace-startup', |
| negatable: true, |
| defaultsTo: false, |
| help: 'Start tracing during startup.'); |
| argParser.addOption('route', |
| help: 'Which route to load when running the app.'); |
| usesTargetOption(); |
| usesPortOptions(); |
| usesPubOption(); |
| } |
| |
| bool get traceStartup => argResults['trace-startup']; |
| String get route => argResults['route']; |
| |
| void usesPortOptions() { |
| argParser.addOption('observatory-port', |
| help: 'Listen to the given port for an observatory debugger connection.\n' |
| 'Specifying port 0 will find a random free port.\n' |
| 'Defaults to the first available port after $kDefaultObservatoryPort.' |
| ); |
| argParser.addOption('diagnostic-port', |
| help: 'Listen to the given port for a diagnostic connection.\n' |
| 'Specifying port 0 will find a random free port.\n' |
| 'Defaults to the first available port after $kDefaultDiagnosticPort.' |
| ); |
| } |
| |
| int get observatoryPort { |
| if (argResults['observatory-port'] != null) { |
| try { |
| return int.parse(argResults['observatory-port']); |
| } catch (error) { |
| throwToolExit('Invalid port for `--observatory-port`: $error'); |
| } |
| } |
| return null; |
| } |
| |
| int get diagnosticPort { |
| if (argResults['diagnostic-port'] != null) { |
| try { |
| return int.parse(argResults['diagnostic-port']); |
| } catch (error) { |
| throwToolExit('Invalid port for `--diagnostic-port`: $error'); |
| } |
| } |
| return null; |
| } |
| } |
| |
| class RunCommand extends RunCommandBase { |
| @override |
| final String name = 'run'; |
| |
| @override |
| final String description = 'Run your Flutter app on an attached device.'; |
| |
| RunCommand({ bool verboseHelp: false }) { |
| requiresPubspecYaml(); |
| |
| argParser.addFlag('full-restart', |
| defaultsTo: true, |
| help: 'Stop any currently running application process before running the app.'); |
| argParser.addFlag('start-paused', |
| defaultsTo: false, |
| negatable: false, |
| help: 'Start in a paused mode and wait for a debugger to connect.'); |
| argParser.addFlag('enable-software-rendering', |
| defaultsTo: false, |
| negatable: false, |
| help: 'Enable rendering using the Skia software backend. This is useful\n' |
| 'when testing Flutter on emulators. By default, Flutter will\n' |
| 'attempt to either use OpenGL or Vulkan and fall back to software\n' |
| 'when neither is available.'); |
| argParser.addFlag('trace-skia', |
| defaultsTo: false, |
| negatable: false, |
| help: 'Enable tracing of Skia code. This is useful when debugging\n' |
| 'the GPU thread. By default, Flutter will not log skia code.'); |
| argParser.addFlag('use-test-fonts', |
| negatable: true, |
| defaultsTo: false, |
| help: 'Enable (and default to) the "Ahem" font. This is a special font\n' |
| 'used in tests to remove any dependencies on the font metrics. It\n' |
| 'is enabled when you use "flutter test". Set this flag when running\n' |
| 'a test using "flutter run" for debugging purposes. This flag is\n' |
| 'only available when running in debug mode.'); |
| argParser.addFlag('build', |
| defaultsTo: true, |
| help: 'If necessary, build the app before running.'); |
| argParser.addOption('use-application-binary', |
| hide: !verboseHelp, |
| help: 'Specify a pre-built application binary to use when running.'); |
| argParser.addFlag('preview-dart-2', |
| hide: !verboseHelp, |
| defaultsTo: false, |
| help: 'Preview Dart 2.0 functionality.'); |
| argParser.addOption('packages', |
| hide: !verboseHelp, |
| help: 'Specify the path to the .packages file.'); |
| argParser.addOption('project-root', |
| hide: !verboseHelp, |
| help: 'Specify the project root directory.'); |
| argParser.addOption('project-assets', |
| hide: !verboseHelp, |
| help: 'Specify the project assets relative to the root directory.'); |
| argParser.addFlag('machine', |
| hide: !verboseHelp, |
| help: 'Handle machine structured JSON command input\n' |
| 'and provide output and progress in machine friendly format.'); |
| argParser.addFlag('hot', |
| negatable: true, |
| defaultsTo: kHotReloadDefault, |
| help: 'Run with support for hot reloading.'); |
| argParser.addOption('pid-file', |
| help: 'Specify a file to write the process id to.\n' |
| 'You can send SIGUSR1 to trigger a hot reload\n' |
| 'and SIGUSR2 to trigger a full restart.'); |
| argParser.addFlag('resident', |
| negatable: true, |
| defaultsTo: true, |
| hide: !verboseHelp, |
| help: 'Stay resident after launching the application.'); |
| |
| argParser.addFlag('benchmark', |
| negatable: false, |
| hide: !verboseHelp, |
| help: 'Enable a benchmarking mode. This will run the given application,\n' |
| 'measure the startup time and the app restart time, write the\n' |
| 'results out to "refresh_benchmark.json", and exit. This flag is\n' |
| 'intended for use in generating automated flutter benchmarks.'); |
| |
| argParser.addOption(FlutterOptions.kExtraFrontEndOptions, hide: true); |
| argParser.addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true); |
| } |
| |
| List<Device> devices; |
| |
| @override |
| Future<String> get usagePath async { |
| final String command = shouldUseHotMode() ? 'hotrun' : name; |
| |
| if (devices == null) |
| return command; |
| |
| // Return 'run/ios'. |
| if (devices.length > 1) |
| return '$command/all'; |
| else |
| return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}'; |
| } |
| |
| @override |
| Future<Map<String, String>> get usageValues async { |
| final bool isEmulator = await devices[0].isLocalEmulator; |
| final String deviceType = devices.length == 1 |
| ? getNameForTargetPlatform(await devices[0].targetPlatform) |
| : 'multiple'; |
| |
| return <String, String>{ 'cd3': '$isEmulator', 'cd4': deviceType }; |
| } |
| |
| @override |
| void printNoConnectedDevices() { |
| super.printNoConnectedDevices(); |
| if (getCurrentHostPlatform() == HostPlatform.darwin_x64 && |
| xcode.isInstalledAndMeetsVersionCheck) { |
| printStatus(''); |
| printStatus('To run on a simulator, launch it first: open -a Simulator.app'); |
| printStatus(''); |
| printStatus('If you expected your device to be detected, please run "flutter doctor" to diagnose'); |
| printStatus('potential issues, or visit https://flutter.io/setup/ for troubleshooting tips.'); |
| } |
| } |
| |
| @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() { |
| final bool hotArg = argResults['hot'] ?? false; |
| final bool shouldUseHotMode = hotArg; |
| return getBuildInfo().isDebug && shouldUseHotMode; |
| } |
| |
| bool get runningWithPrebuiltApplication => |
| argResults['use-application-binary'] != null; |
| |
| bool get stayResident => argResults['resident']; |
| |
| @override |
| Future<Null> 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 (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication) |
| throwToolExit('Using -d all with --use-application-binary is not supported'); |
| } |
| |
| DebuggingOptions _createDebuggingOptions() { |
| final BuildInfo buildInfo = getBuildInfo(); |
| if (buildInfo.isRelease) { |
| return new DebuggingOptions.disabled(buildInfo); |
| } else { |
| return new DebuggingOptions.enabled( |
| buildInfo, |
| startPaused: argResults['start-paused'], |
| useTestFonts: argResults['use-test-fonts'], |
| enableSoftwareRendering: argResults['enable-software-rendering'], |
| traceSkia: argResults['trace-skia'], |
| observatoryPort: observatoryPort, |
| diagnosticPort: diagnosticPort, |
| ); |
| } |
| } |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| Cache.releaseLockEarly(); |
| |
| // Enable hot mode by default if `--no-hot` was not passed and we are in |
| // debug mode. |
| final bool hotMode = shouldUseHotMode(); |
| |
| if (argResults['machine']) { |
| if (devices.length > 1) |
| throwToolExit('--machine does not support -d all.'); |
| final Daemon daemon = new Daemon(stdinCommandStream, stdoutCommandResponse, |
| notifyingLogger: new NotifyingLogger(), logToStdout: true); |
| AppInstance app; |
| try { |
| app = await daemon.appDomain.startApp( |
| devices.first, fs.currentDirectory.path, targetFile, route, |
| _createDebuggingOptions(), hotMode, |
| applicationBinary: argResults['use-application-binary'], |
| projectRootPath: argResults['project-root'], |
| packagesFilePath: argResults['packages'], |
| projectAssets: argResults['project-assets'] |
| ); |
| } catch (error) { |
| throwToolExit(error.toString()); |
| } |
| final DateTime appStartedTime = clock.now(); |
| final int result = await app.runner.waitForAppToFinish(); |
| if (result != 0) |
| throwToolExit(null, exitCode: result); |
| return new FlutterCommandResult( |
| ExitStatus.success, |
| timingLabelParts: <String>['daemon'], |
| endTimeOverride: appStartedTime, |
| ); |
| } |
| |
| for (Device device in devices) { |
| if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode())) |
| throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.'); |
| } |
| |
| if (hotMode) { |
| for (Device device in devices) { |
| if (!device.supportsHotMode) |
| throwToolExit('Hot mode is not supported by ${device.name}. Run with --no-hot.'); |
| } |
| } |
| |
| final String pidFile = argResults['pid-file']; |
| if (pidFile != null) { |
| // Write our pid to the file. |
| fs.file(pidFile).writeAsStringSync(pid.toString()); |
| } |
| |
| final List<FlutterDevice> flutterDevices = devices.map((Device device) { |
| return new FlutterDevice(device, previewDart2: argResults['preview-dart-2']); |
| }).toList(); |
| |
| ResidentRunner runner; |
| if (hotMode) { |
| runner = new HotRunner( |
| flutterDevices, |
| target: targetFile, |
| debuggingOptions: _createDebuggingOptions(), |
| benchmarkMode: argResults['benchmark'], |
| applicationBinary: argResults['use-application-binary'], |
| previewDart2: argResults['preview-dart-2'], |
| projectRootPath: argResults['project-root'], |
| packagesFilePath: argResults['packages'], |
| projectAssets: argResults['project-assets'], |
| stayResident: stayResident, |
| ); |
| } else { |
| runner = new ColdRunner( |
| flutterDevices, |
| target: targetFile, |
| debuggingOptions: _createDebuggingOptions(), |
| traceStartup: traceStartup, |
| applicationBinary: argResults['use-application-binary'], |
| previewDart2: argResults['preview-dart-2'], |
| stayResident: stayResident, |
| ); |
| } |
| |
| 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<Null> appStartedTimeRecorder = new Completer<Null>.sync(); |
| // This callback can't throw. |
| appStartedTimeRecorder.future.then( // ignore: unawaited_futures |
| (_) { appStartedTime = clock.now(); } |
| ); |
| |
| final int result = await runner.run( |
| appStartedCompleter: appStartedTimeRecorder, |
| route: route, |
| shouldBuild: !runningWithPrebuiltApplication && argResults['build'], |
| ); |
| if (result != 0) |
| throwToolExit(null, exitCode: result); |
| return new FlutterCommandResult( |
| ExitStatus.success, |
| timingLabelParts: <String>[ |
| hotMode ? 'hot' : 'cold', |
| getModeName(getBuildMode()), |
| devices.length == 1 |
| ? getNameForTargetPlatform(await devices[0].targetPlatform) |
| : 'multiple', |
| devices.length == 1 && await devices[0].isLocalEmulator ? 'emulator' : null |
| ], |
| endTimeOverride: appStartedTime, |
| ); |
| } |
| } |