mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_tools] Adds some support for '-d all' (#9585)
This commit is contained in:
parent
9558ac7d55
commit
0770c3c14f
@ -353,11 +353,13 @@ class AppDomain extends Domain {
|
||||
final Directory cwd = fs.currentDirectory;
|
||||
fs.currentDirectory = fs.directory(projectDirectory);
|
||||
|
||||
final FlutterDevice flutterDevice = new FlutterDevice(device);
|
||||
|
||||
ResidentRunner runner;
|
||||
|
||||
if (enableHotReload) {
|
||||
runner = new HotRunner(
|
||||
device,
|
||||
<FlutterDevice>[flutterDevice],
|
||||
target: target,
|
||||
debuggingOptions: options,
|
||||
usesTerminalUI: false,
|
||||
@ -368,7 +370,7 @@ class AppDomain extends Domain {
|
||||
);
|
||||
} else {
|
||||
runner = new ColdRunner(
|
||||
device,
|
||||
<FlutterDevice>[flutterDevice],
|
||||
target: target,
|
||||
debuggingOptions: options,
|
||||
usesTerminalUI: false,
|
||||
@ -448,7 +450,7 @@ class AppDomain extends Domain {
|
||||
if (app == null)
|
||||
throw "app '$appId' not found";
|
||||
|
||||
final Isolate isolate = app.runner.currentView.uiIsolate;
|
||||
final Isolate isolate = app.runner.flutterDevices.first.views.first.uiIsolate;
|
||||
final Map<String, dynamic> result = await isolate.invokeFlutterExtensionRpcRaw(methodName, params: params);
|
||||
if (result == null)
|
||||
return new OperationResult(1, 'method not available: $methodName');
|
||||
|
@ -16,6 +16,7 @@ import '../device.dart';
|
||||
import '../flx.dart' as flx;
|
||||
import '../fuchsia/fuchsia_device.dart';
|
||||
import '../globals.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_hot.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import '../vmservice.dart';
|
||||
@ -112,18 +113,21 @@ class FuchsiaReloadCommand extends FlutterCommand {
|
||||
final List<String> fullAddresses = targetPorts.map(
|
||||
(int p) => '$_address:$p'
|
||||
).toList();
|
||||
final List<Uri> observatoryUris = fullAddresses.map(
|
||||
(String a) => Uri.parse('http://$a')
|
||||
).toList();
|
||||
final FuchsiaDevice device = new FuchsiaDevice(fullAddresses[0]);
|
||||
final FlutterDevice flutterDevice = new FlutterDevice(device);
|
||||
flutterDevice.observatoryUris = observatoryUris;
|
||||
final HotRunner hotRunner = new HotRunner(
|
||||
device,
|
||||
<FlutterDevice>[flutterDevice],
|
||||
debuggingOptions: new DebuggingOptions.enabled(getBuildMode()),
|
||||
target: _target,
|
||||
projectRootPath: _fuchsiaProjectPath,
|
||||
packagesFilePath: _dotPackagesPath
|
||||
);
|
||||
final List<Uri> observatoryUris =
|
||||
fullAddresses.map((String a) => Uri.parse('http://$a')).toList();
|
||||
printStatus('Connecting to $_binaryName');
|
||||
await hotRunner.attach(observatoryUris, isolateFilter: isolateName);
|
||||
await hotRunner.attach(viewFilter: isolateName);
|
||||
}
|
||||
|
||||
// A cache of VMService connections.
|
||||
@ -151,12 +155,12 @@ class FuchsiaReloadCommand extends FlutterCommand {
|
||||
}
|
||||
|
||||
// Find ports where there is a view isolate with the given name
|
||||
Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async {
|
||||
Future<List<int>> _filterPorts(List<int> ports, String viewFilter) async {
|
||||
final List<int> result = <int>[];
|
||||
for (FlutterView v in await _getViews(ports)) {
|
||||
final Uri addr = v.owner.vmService.httpAddress;
|
||||
printTrace('At $addr, found view: ${v.uiIsolate.name}');
|
||||
if (v.uiIsolate.name.indexOf(isolateFilter) == 0)
|
||||
if (v.uiIsolate.name.indexOf(viewFilter) == 0)
|
||||
result.add(addr.port);
|
||||
}
|
||||
return result;
|
||||
|
@ -150,17 +150,20 @@ class RunCommand extends RunCommandBase {
|
||||
};
|
||||
}
|
||||
|
||||
Device device;
|
||||
List<Device> devices;
|
||||
|
||||
@override
|
||||
Future<String> get usagePath async {
|
||||
final String command = shouldUseHotMode() ? 'hotrun' : name;
|
||||
|
||||
if (device == null)
|
||||
if (devices == null)
|
||||
return command;
|
||||
|
||||
// Return 'run/ios'.
|
||||
return '$command/${getNameForTargetPlatform(await device.targetPlatform)}';
|
||||
if (devices.length > 1)
|
||||
return '$command/all';
|
||||
else
|
||||
return '$command/${getNameForTargetPlatform(await devices[0].targetPlatform)}';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -199,9 +202,11 @@ class RunCommand extends RunCommandBase {
|
||||
@override
|
||||
Future<Null> verifyThenRunCommand() async {
|
||||
commandValidator();
|
||||
device = await findTargetDevice();
|
||||
if (device == null)
|
||||
devices = await findAllTargetDevices();
|
||||
if (devices == null)
|
||||
throwToolExit(null);
|
||||
if (deviceManager.hasSpecifiedAllDevices && runningWithPrebuiltApplication)
|
||||
throwToolExit('Using -d all with --use-application-binary is not supported');
|
||||
return super.verifyThenRunCommand();
|
||||
}
|
||||
|
||||
@ -221,7 +226,6 @@ class RunCommand extends RunCommandBase {
|
||||
|
||||
@override
|
||||
Future<Null> runCommand() async {
|
||||
|
||||
Cache.releaseLockEarly();
|
||||
|
||||
// Enable hot mode by default if `--no-hot` was not passed and we are in
|
||||
@ -229,17 +233,20 @@ class RunCommand extends RunCommandBase {
|
||||
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(
|
||||
device, fs.currentDirectory.path, targetFile, route,
|
||||
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']);
|
||||
projectAssets: argResults['project-assets']
|
||||
);
|
||||
} catch (error) {
|
||||
throwToolExit(error.toString());
|
||||
}
|
||||
@ -249,12 +256,16 @@ class RunCommand extends RunCommandBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
|
||||
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
|
||||
for (Device device in devices) {
|
||||
if (await device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
|
||||
throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
|
||||
}
|
||||
|
||||
if (hotMode) {
|
||||
if (!device.supportsHotMode)
|
||||
throwToolExit('Hot mode is not supported by this device. Run with --no-hot.');
|
||||
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'];
|
||||
@ -262,11 +273,15 @@ class RunCommand extends RunCommandBase {
|
||||
// Write our pid to the file.
|
||||
fs.file(pidFile).writeAsStringSync(pid.toString());
|
||||
}
|
||||
ResidentRunner runner;
|
||||
|
||||
final List<FlutterDevice> flutterDevices = devices.map((Device device) {
|
||||
return new FlutterDevice(device);
|
||||
}).toList();
|
||||
|
||||
ResidentRunner runner;
|
||||
if (hotMode) {
|
||||
runner = new HotRunner(
|
||||
device,
|
||||
flutterDevices,
|
||||
target: targetFile,
|
||||
debuggingOptions: _createDebuggingOptions(),
|
||||
benchmarkMode: argResults['benchmark'],
|
||||
@ -279,7 +294,7 @@ class RunCommand extends RunCommandBase {
|
||||
);
|
||||
} else {
|
||||
runner = new ColdRunner(
|
||||
device,
|
||||
flutterDevices,
|
||||
target: targetFile,
|
||||
debuggingOptions: _createDebuggingOptions(),
|
||||
traceStartup: traceStartup,
|
||||
|
@ -32,11 +32,26 @@ class DeviceManager {
|
||||
|
||||
final List<DeviceDiscovery> _deviceDiscoverers = <DeviceDiscovery>[];
|
||||
|
||||
/// A user-specified device ID.
|
||||
String specifiedDeviceId;
|
||||
String _specifiedDeviceId;
|
||||
|
||||
/// A user-specified device ID.
|
||||
String get specifiedDeviceId {
|
||||
if (_specifiedDeviceId == null || _specifiedDeviceId == 'all')
|
||||
return null;
|
||||
return _specifiedDeviceId;
|
||||
}
|
||||
|
||||
set specifiedDeviceId(String id) {
|
||||
_specifiedDeviceId = id;
|
||||
}
|
||||
|
||||
/// True when the user has specified a single specific device.
|
||||
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
|
||||
|
||||
/// True when the user has specified all devices by setting
|
||||
/// specifiedDeviceId = 'all'.
|
||||
bool get hasSpecifiedAllDevices => _specifiedDeviceId == 'all';
|
||||
|
||||
Stream<Device> getDevicesById(String deviceId) async* {
|
||||
final Stream<Device> devices = getAllConnectedDevices();
|
||||
deviceId = deviceId.toLowerCase();
|
||||
|
@ -18,13 +18,320 @@ import 'build_info.dart';
|
||||
import 'dart/dependencies.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'dependency_checker.dart';
|
||||
import 'devfs.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart';
|
||||
import 'run_cold.dart';
|
||||
import 'run_hot.dart';
|
||||
import 'vmservice.dart';
|
||||
|
||||
class FlutterDevice {
|
||||
final Device device;
|
||||
List<Uri> observatoryUris;
|
||||
List<VMService> vmServices;
|
||||
DevFS devFS;
|
||||
ApplicationPackage package;
|
||||
|
||||
String _viewFilter;
|
||||
StreamSubscription<String> _loggingSubscription;
|
||||
|
||||
FlutterDevice(this.device);
|
||||
|
||||
String get viewFilter => _viewFilter;
|
||||
set viewFilter(String filter) {
|
||||
_viewFilter = filter;
|
||||
_viewsCache = null;
|
||||
}
|
||||
|
||||
void connect() {
|
||||
if (vmServices != null)
|
||||
return;
|
||||
vmServices = new List<VMService>(observatoryUris.length);
|
||||
for (int i = 0; i < observatoryUris.length; i++) {
|
||||
vmServices[i] = VMService.connect(observatoryUris[i]);
|
||||
printTrace('Connected to service protocol: ${observatoryUris[i]}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> refreshViews() async {
|
||||
if ((vmServices == null) || vmServices.isEmpty)
|
||||
return;
|
||||
for (VMService service in vmServices)
|
||||
await service.vm.refreshViews();
|
||||
_viewsCache = null;
|
||||
}
|
||||
|
||||
List<FlutterView> _viewsCache;
|
||||
List<FlutterView> get views {
|
||||
if (_viewsCache == null) {
|
||||
if ((vmServices == null) || vmServices.isEmpty)
|
||||
return null;
|
||||
final List<FlutterView> result = <FlutterView>[];
|
||||
if (_viewFilter == null) {
|
||||
for (VMService service in vmServices) {
|
||||
if (!service.isClosed)
|
||||
result.addAll(service.vm.views.toList());
|
||||
}
|
||||
} else {
|
||||
for (VMService service in vmServices) {
|
||||
if (!service.isClosed)
|
||||
result.addAll(service.vm.allViewsWithName(_viewFilter));
|
||||
}
|
||||
}
|
||||
_viewsCache = result;
|
||||
}
|
||||
return _viewsCache;
|
||||
}
|
||||
|
||||
Future<Null> getVMs() async {
|
||||
for (VMService service in vmServices)
|
||||
await service.getVM();
|
||||
}
|
||||
|
||||
Future<Null> waitForViews() async {
|
||||
// Refresh the view list, and wait a bit for the list to populate.
|
||||
for (VMService service in vmServices)
|
||||
await service.waitForViews();
|
||||
}
|
||||
|
||||
Future<Null> stopApps() async {
|
||||
final List<FlutterView> flutterViews = views;
|
||||
if (flutterViews == null || flutterViews.isEmpty)
|
||||
return;
|
||||
for (FlutterView view in flutterViews) {
|
||||
if (view != null && view.uiIsolate != null)
|
||||
view.uiIsolate.flutterExit();
|
||||
}
|
||||
await new Future<Null>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
Future<Uri> setupDevFS(String fsName,
|
||||
Directory rootDirectory, {
|
||||
String packagesFilePath
|
||||
}) {
|
||||
// One devFS per device. Shared by all running instances.
|
||||
devFS = new DevFS(
|
||||
vmServices[0],
|
||||
fsName,
|
||||
rootDirectory,
|
||||
packagesFilePath: packagesFilePath
|
||||
);
|
||||
return devFS.create();
|
||||
}
|
||||
|
||||
List<Future<Map<String, dynamic>>> reloadSources(
|
||||
String entryPath, {
|
||||
bool pause: false
|
||||
}) {
|
||||
final Uri deviceEntryUri = devFS.baseUri.resolveUri(fs.path.toUri(entryPath));
|
||||
final Uri devicePackagesUri = devFS.baseUri.resolve('.packages');
|
||||
final List<Future<Map<String, dynamic>>> reports = <Future<Map<String, dynamic>>>[];
|
||||
for (FlutterView view in views) {
|
||||
final Future<Map<String, dynamic>> report = view.uiIsolate.reloadSources(
|
||||
pause: pause,
|
||||
rootLibUri: deviceEntryUri,
|
||||
packagesUri: devicePackagesUri
|
||||
);
|
||||
reports.add(report);
|
||||
}
|
||||
return reports;
|
||||
}
|
||||
|
||||
Future<Null> debugDumpApp() async {
|
||||
for (FlutterView view in views)
|
||||
await view.uiIsolate.flutterDebugDumpApp();
|
||||
}
|
||||
|
||||
Future<Null> debugDumpRenderTree() async {
|
||||
for (FlutterView view in views)
|
||||
await view.uiIsolate.flutterDebugDumpRenderTree();
|
||||
}
|
||||
|
||||
Future<Null> toggleDebugPaintSizeEnabled() async {
|
||||
for (FlutterView view in views)
|
||||
await view.uiIsolate.flutterToggleDebugPaintSizeEnabled();
|
||||
}
|
||||
|
||||
Future<String> togglePlatform({String from}) async {
|
||||
String to;
|
||||
switch (from) {
|
||||
case 'iOS':
|
||||
to = 'android';
|
||||
break;
|
||||
case 'android':
|
||||
default:
|
||||
to = 'iOS';
|
||||
break;
|
||||
}
|
||||
for (FlutterView view in views) {
|
||||
await view.uiIsolate.flutterPlatformOverride(to);
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
void startEchoingDeviceLog() {
|
||||
if (_loggingSubscription != null)
|
||||
return;
|
||||
_loggingSubscription = device.getLogReader(app: package).logLines.listen((String line) {
|
||||
if (!line.contains('Observatory listening on http') &&
|
||||
!line.contains('Diagnostic server listening on http'))
|
||||
printStatus(line);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> stopEchoingDeviceLog() async {
|
||||
if (_loggingSubscription == null)
|
||||
return;
|
||||
await _loggingSubscription.cancel();
|
||||
_loggingSubscription = null;
|
||||
}
|
||||
|
||||
void initLogReader() {
|
||||
device.getLogReader(app: package).appPid = vmServices.first.vm.pid;
|
||||
}
|
||||
|
||||
Future<int> runHot({
|
||||
HotRunner hotRunner,
|
||||
String route,
|
||||
bool shouldBuild,
|
||||
}) async {
|
||||
final bool prebuiltMode = hotRunner.applicationBinary != null;
|
||||
final String modeName = getModeName(hotRunner.debuggingOptions.buildMode);
|
||||
printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...');
|
||||
|
||||
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||
package = getApplicationPackageForPlatform(
|
||||
targetPlatform,
|
||||
applicationBinary: hotRunner.applicationBinary
|
||||
);
|
||||
|
||||
if (package == null) {
|
||||
String message = 'No application found for $targetPlatform.';
|
||||
final String hint = getMissingPackageHintForPlatform(targetPlatform);
|
||||
if (hint != null)
|
||||
message += '\n$hint';
|
||||
printError(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> platformArgs = <String, dynamic>{};
|
||||
|
||||
startEchoingDeviceLog();
|
||||
|
||||
// Start the application.
|
||||
final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this);
|
||||
final Future<LaunchResult> futureResult = device.startApp(
|
||||
package,
|
||||
hotRunner.debuggingOptions.buildMode,
|
||||
mainPath: hotRunner.mainPath,
|
||||
debuggingOptions: hotRunner.debuggingOptions,
|
||||
platformArgs: platformArgs,
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
kernelPath: hotRunner.kernelFilePath,
|
||||
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies
|
||||
);
|
||||
|
||||
final LaunchResult result = await futureResult;
|
||||
|
||||
if (!result.started) {
|
||||
printError('Error launching application on ${device.name}.');
|
||||
await stopEchoingDeviceLog();
|
||||
return 2;
|
||||
}
|
||||
observatoryUris = <Uri>[result.observatoryUri];
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Future<int> runCold({
|
||||
ColdRunner coldRunner,
|
||||
String route,
|
||||
bool shouldBuild: true,
|
||||
}) async {
|
||||
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||
package = getApplicationPackageForPlatform(
|
||||
targetPlatform,
|
||||
applicationBinary: coldRunner.applicationBinary
|
||||
);
|
||||
|
||||
final String modeName = getModeName(coldRunner.debuggingOptions.buildMode);
|
||||
final bool prebuiltMode = coldRunner.applicationBinary != null;
|
||||
if (coldRunner.mainPath == null) {
|
||||
assert(prebuiltMode);
|
||||
printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
|
||||
} else {
|
||||
printStatus('Launching ${getDisplayPath(coldRunner.mainPath)} on ${device.name} in $modeName mode...');
|
||||
}
|
||||
|
||||
if (package == null) {
|
||||
String message = 'No application found for $targetPlatform.';
|
||||
final String hint = getMissingPackageHintForPlatform(targetPlatform);
|
||||
if (hint != null)
|
||||
message += '\n$hint';
|
||||
printError(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Map<String, dynamic> platformArgs;
|
||||
if (coldRunner.traceStartup != null)
|
||||
platformArgs = <String, dynamic>{ 'trace-startup': coldRunner.traceStartup };
|
||||
|
||||
startEchoingDeviceLog();
|
||||
|
||||
final bool hasDirtyDependencies = coldRunner.hasDirtyDependencies(this);
|
||||
final LaunchResult result = await device.startApp(
|
||||
package,
|
||||
coldRunner.debuggingOptions.buildMode,
|
||||
mainPath: coldRunner.mainPath,
|
||||
debuggingOptions: coldRunner.debuggingOptions,
|
||||
platformArgs: platformArgs,
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies
|
||||
);
|
||||
|
||||
if (!result.started) {
|
||||
printError('Error running application on ${device.name}.');
|
||||
await stopEchoingDeviceLog();
|
||||
return 2;
|
||||
}
|
||||
if (result.hasObservatory)
|
||||
observatoryUris = <Uri>[result.observatoryUri];
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<bool> updateDevFS({
|
||||
DevFSProgressReporter progressReporter,
|
||||
AssetBundle bundle,
|
||||
bool bundleDirty: false,
|
||||
Set<String> fileFilter
|
||||
}) async {
|
||||
final Status devFSStatus = logger.startProgress(
|
||||
'Syncing files to device ${device.name}...',
|
||||
expectSlowOperation: true
|
||||
);
|
||||
int bytes = 0;
|
||||
try {
|
||||
bytes = await devFS.update(
|
||||
progressReporter: progressReporter,
|
||||
bundle: bundle,
|
||||
bundleDirty: bundleDirty,
|
||||
fileFilter: fileFilter
|
||||
);
|
||||
} on DevFSException {
|
||||
devFSStatus.cancel();
|
||||
return false;
|
||||
}
|
||||
devFSStatus.stop();
|
||||
printTrace('Synced ${getSizeAsMB(bytes)}.');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Shared code between different resident application runners.
|
||||
abstract class ResidentRunner {
|
||||
ResidentRunner(this.device, {
|
||||
ResidentRunner(this.flutterDevices, {
|
||||
this.target,
|
||||
this.debuggingOptions,
|
||||
this.usesTerminalUI: true,
|
||||
@ -43,7 +350,7 @@ abstract class ResidentRunner {
|
||||
_assetBundle = new AssetBundle();
|
||||
}
|
||||
|
||||
final Device device;
|
||||
final List<FlutterDevice> flutterDevices;
|
||||
final String target;
|
||||
final DebuggingOptions debuggingOptions;
|
||||
final bool usesTerminalUI;
|
||||
@ -58,16 +365,12 @@ abstract class ResidentRunner {
|
||||
String get mainPath => _mainPath;
|
||||
AssetBundle _assetBundle;
|
||||
AssetBundle get assetBundle => _assetBundle;
|
||||
ApplicationPackage package;
|
||||
|
||||
bool get isRunningDebug => debuggingOptions.buildMode == BuildMode.debug;
|
||||
bool get isRunningProfile => debuggingOptions.buildMode == BuildMode.profile;
|
||||
bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release;
|
||||
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
|
||||
|
||||
List<VMService> vmServices;
|
||||
StreamSubscription<String> _loggingSubscription;
|
||||
|
||||
/// Start the app and keep the process running during its lifetime.
|
||||
Future<int> run({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
@ -78,25 +381,6 @@ abstract class ResidentRunner {
|
||||
|
||||
bool get supportsRestart => false;
|
||||
|
||||
String isolateFilter;
|
||||
|
||||
List<FlutterView> _currentViewsCache;
|
||||
List<FlutterView> get currentViews {
|
||||
if (_currentViewsCache == null) {
|
||||
if ((vmServices == null) || vmServices.isEmpty)
|
||||
return null;
|
||||
if (isolateFilter == null)
|
||||
return vmServices[0].vm.views.toList();
|
||||
final List<FlutterView> result = <FlutterView>[];
|
||||
for (VMService service in vmServices)
|
||||
result.addAll(service.vm.allViewsWithName(isolateFilter));
|
||||
_currentViewsCache = result;
|
||||
}
|
||||
return _currentViewsCache;
|
||||
}
|
||||
|
||||
FlutterView get currentView => (currentViews != null) ? currentViews[0] : null;
|
||||
|
||||
Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) {
|
||||
throw 'unsupported';
|
||||
}
|
||||
@ -115,47 +399,49 @@ abstract class ResidentRunner {
|
||||
}
|
||||
|
||||
Future<Null> refreshViews() async {
|
||||
if ((vmServices == null) || vmServices.isEmpty)
|
||||
return;
|
||||
for (VMService service in vmServices)
|
||||
await service.vm.refreshViews();
|
||||
_currentViewsCache = null;
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
await device.refreshViews();
|
||||
}
|
||||
|
||||
Future<Null> _debugDumpApp() async {
|
||||
await refreshViews();
|
||||
await currentView.uiIsolate.flutterDebugDumpApp();
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
await device.debugDumpApp();
|
||||
}
|
||||
|
||||
Future<Null> _debugDumpRenderTree() async {
|
||||
await refreshViews();
|
||||
await currentView.uiIsolate.flutterDebugDumpRenderTree();
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
await device.debugDumpRenderTree();
|
||||
}
|
||||
|
||||
Future<Null> _debugToggleDebugPaintSizeEnabled() async {
|
||||
await refreshViews();
|
||||
await currentView.uiIsolate.flutterToggleDebugPaintSizeEnabled();
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
await device.toggleDebugPaintSizeEnabled();
|
||||
}
|
||||
|
||||
Future<Null> _screenshot() async {
|
||||
final Status status = logger.startProgress('Taking screenshot...');
|
||||
Future<Null> _screenshot(FlutterDevice device) async {
|
||||
final Status status = logger.startProgress('Taking screenshot for ${device.device.name}...');
|
||||
final File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png');
|
||||
try {
|
||||
if (supportsServiceProtocol && isRunningDebug) {
|
||||
await refreshViews();
|
||||
await device.refreshViews();
|
||||
try {
|
||||
await currentView.uiIsolate.flutterDebugAllowBanner(false);
|
||||
for (FlutterView view in device.views)
|
||||
await view.uiIsolate.flutterDebugAllowBanner(false);
|
||||
} catch (error) {
|
||||
status.stop();
|
||||
printError(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await device.takeScreenshot(outputFile);
|
||||
await device.device.takeScreenshot(outputFile);
|
||||
} finally {
|
||||
if (supportsServiceProtocol && isRunningDebug) {
|
||||
try {
|
||||
await currentView.uiIsolate.flutterDebugAllowBanner(true);
|
||||
for (FlutterView view in device.views)
|
||||
await view.uiIsolate.flutterDebugAllowBanner(true);
|
||||
} catch (error) {
|
||||
status.stop();
|
||||
printError(error);
|
||||
@ -171,15 +457,13 @@ abstract class ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _debugRotatePlatform() async {
|
||||
Future<Null> _debugTogglePlatform() async {
|
||||
await refreshViews();
|
||||
switch (await currentView.uiIsolate.flutterPlatformOverride()) {
|
||||
case 'iOS':
|
||||
return await currentView.uiIsolate.flutterPlatformOverride('android');
|
||||
case 'android':
|
||||
default:
|
||||
return await currentView.uiIsolate.flutterPlatformOverride('iOS');
|
||||
}
|
||||
final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride();
|
||||
String to;
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
to = await device.togglePlatform(from: from);
|
||||
printStatus('Switched operating system to $to');
|
||||
}
|
||||
|
||||
void registerSignalHandlers() {
|
||||
@ -215,53 +499,38 @@ abstract class ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> startEchoingDeviceLog(ApplicationPackage app) async {
|
||||
if (_loggingSubscription != null)
|
||||
return;
|
||||
_loggingSubscription = device.getLogReader(app: app).logLines.listen((String line) {
|
||||
if (!line.contains('Observatory listening on http') &&
|
||||
!line.contains('Diagnostic server listening on http'))
|
||||
printStatus(line);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> stopEchoingDeviceLog() async {
|
||||
if (_loggingSubscription != null) {
|
||||
await _loggingSubscription.cancel();
|
||||
}
|
||||
_loggingSubscription = null;
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
device.stopEchoingDeviceLog();
|
||||
}
|
||||
|
||||
Future<Null> connectToServiceProtocol(
|
||||
List<Uri> uris, {
|
||||
String isolateFilter
|
||||
}) async {
|
||||
Future<Null> connectToServiceProtocol({String viewFilter}) async {
|
||||
if (!debuggingOptions.debuggingEnabled) {
|
||||
return new Future<Null>.error('Error the service protocol is not enabled.');
|
||||
}
|
||||
final List<VMService> services = new List<VMService>(uris.length);
|
||||
for (int i = 0; i < uris.length; i++) {
|
||||
services[i] = VMService.connect(uris[i]);
|
||||
printTrace('Connected to service protocol: ${uris[i]}');
|
||||
|
||||
bool viewFound = false;
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
device.viewFilter = viewFilter;
|
||||
device.connect();
|
||||
await device.getVMs();
|
||||
await device.waitForViews();
|
||||
if (device.views == null)
|
||||
printStatus('No Flutter views available on ${device.device.name}');
|
||||
else
|
||||
viewFound = true;
|
||||
}
|
||||
vmServices = services;
|
||||
for (VMService service in services)
|
||||
await service.getVM();
|
||||
|
||||
this.isolateFilter = isolateFilter;
|
||||
|
||||
// Refresh the view list, and wait a bit for the list to populate.
|
||||
for (VMService service in services)
|
||||
await service.waitForViews();
|
||||
if (currentView == null)
|
||||
if (!viewFound)
|
||||
throwToolExit('No Flutter view is available');
|
||||
|
||||
// Listen for service protocol connection to close.
|
||||
for (VMService service in services) {
|
||||
service.done.then<Null>(
|
||||
_serviceProtocolDone,
|
||||
onError: _serviceProtocolError
|
||||
).whenComplete(_serviceDisconnected);
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (VMService service in device.vmServices) {
|
||||
service.done.then<Null>(
|
||||
_serviceProtocolDone,
|
||||
onError: _serviceProtocolError
|
||||
).whenComplete(_serviceDisconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,14 +570,14 @@ abstract class ResidentRunner {
|
||||
return true;
|
||||
}
|
||||
} else if (lower == 's') {
|
||||
if (device.supportsScreenshot) {
|
||||
await _screenshot();
|
||||
return true;
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.device.supportsScreenshot)
|
||||
await _screenshot(device);
|
||||
}
|
||||
return true;
|
||||
} else if (lower == 'o') {
|
||||
if (supportsServiceProtocol && isRunningDebug) {
|
||||
final String platform = await _debugRotatePlatform();
|
||||
print('Switched operating system to: $platform');
|
||||
await _debugTogglePlatform();
|
||||
return true;
|
||||
}
|
||||
} else if (lower == 'q') {
|
||||
@ -381,12 +650,12 @@ abstract class ResidentRunner {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
bool hasDirtyDependencies() {
|
||||
bool hasDirtyDependencies(FlutterDevice device) {
|
||||
final DartDependencySetBuilder dartDependencySetBuilder =
|
||||
new DartDependencySetBuilder(mainPath, packagesFilePath);
|
||||
final DependencyChecker dependencyChecker =
|
||||
new DependencyChecker(dartDependencySetBuilder, assetBundle);
|
||||
final String path = package.packagePath;
|
||||
final String path = device.package.packagePath;
|
||||
if (path == null) {
|
||||
return true;
|
||||
}
|
||||
@ -404,16 +673,8 @@ abstract class ResidentRunner {
|
||||
Future<Null> preStop() async { }
|
||||
|
||||
Future<Null> stopApp() async {
|
||||
if (vmServices != null &&
|
||||
vmServices.isNotEmpty &&
|
||||
!vmServices[0].isClosed) {
|
||||
// TODO(zra): iterate over all the views.
|
||||
if ((currentView != null) && (currentView.uiIsolate != null)) {
|
||||
// TODO(johnmccutchan): Wait for the exit command to complete.
|
||||
currentView.uiIsolate.flutterExit();
|
||||
await new Future<Null>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
await device.stopApps();
|
||||
appFinished();
|
||||
}
|
||||
|
||||
@ -429,7 +690,7 @@ abstract class ResidentRunner {
|
||||
printStatus('To simulate different operating systems, (defaultTargetPlatform), press "o".');
|
||||
}
|
||||
}
|
||||
if (device.supportsScreenshot)
|
||||
if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot))
|
||||
printStatus('To save a screenshot to flutter.png, press "s".');
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'application_package.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'build_info.dart';
|
||||
import 'commands/trace.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart';
|
||||
@ -17,25 +14,22 @@ import 'resident_runner.dart';
|
||||
|
||||
class ColdRunner extends ResidentRunner {
|
||||
ColdRunner(
|
||||
Device device, {
|
||||
List<FlutterDevice> devices, {
|
||||
String target,
|
||||
DebuggingOptions debuggingOptions,
|
||||
bool usesTerminalUI: true,
|
||||
this.traceStartup: false,
|
||||
this.applicationBinary,
|
||||
bool stayResident: true,
|
||||
}) : super(device,
|
||||
}) : super(devices,
|
||||
target: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
usesTerminalUI: usesTerminalUI,
|
||||
stayResident: stayResident);
|
||||
|
||||
LaunchResult _result;
|
||||
final bool traceStartup;
|
||||
final String applicationBinary;
|
||||
|
||||
bool get prebuiltMode => applicationBinary != null;
|
||||
|
||||
@override
|
||||
Future<int> run({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
@ -43,6 +37,7 @@ class ColdRunner extends ResidentRunner {
|
||||
String route,
|
||||
bool shouldBuild: true
|
||||
}) async {
|
||||
final bool prebuiltMode = applicationBinary != null;
|
||||
if (!prebuiltMode) {
|
||||
if (!fs.isFileSync(mainPath)) {
|
||||
String message = 'Tried to run $mainPath, but that file does not exist.';
|
||||
@ -53,79 +48,49 @@ class ColdRunner extends ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
final String modeName = getModeName(debuggingOptions.buildMode);
|
||||
if (mainPath == null) {
|
||||
assert(prebuiltMode);
|
||||
printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
|
||||
} else {
|
||||
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final int result = await device.runCold(
|
||||
coldRunner: this,
|
||||
route: route,
|
||||
shouldBuild: shouldBuild,
|
||||
);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||
package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
|
||||
|
||||
if (package == null) {
|
||||
String message = 'No application found for $targetPlatform.';
|
||||
final String hint = getMissingPackageHintForPlatform(targetPlatform);
|
||||
if (hint != null)
|
||||
message += '\n$hint';
|
||||
printError(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
final Stopwatch startTime = new Stopwatch()..start();
|
||||
|
||||
Map<String, dynamic> platformArgs;
|
||||
if (traceStartup != null)
|
||||
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
|
||||
|
||||
await startEchoingDeviceLog(package);
|
||||
|
||||
_result = await device.startApp(
|
||||
package,
|
||||
debuggingOptions.buildMode,
|
||||
mainPath: mainPath,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: platformArgs,
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies()
|
||||
);
|
||||
|
||||
if (!_result.started) {
|
||||
printError('Error running application on ${device.name}.');
|
||||
await stopEchoingDeviceLog();
|
||||
return 2;
|
||||
}
|
||||
|
||||
startTime.stop();
|
||||
|
||||
// Connect to observatory.
|
||||
if (debuggingOptions.debuggingEnabled)
|
||||
await connectToServiceProtocol(<Uri>[_result.observatoryUri]);
|
||||
await connectToServiceProtocol();
|
||||
|
||||
if (_result.hasObservatory) {
|
||||
if (flutterDevices.first.observatoryUris != null) {
|
||||
// For now, only support one debugger connection.
|
||||
connectionInfoCompleter?.complete(new DebugConnectionInfo(
|
||||
httpUri: _result.observatoryUri,
|
||||
wsUri: vmServices[0].wsAddress,
|
||||
httpUri: flutterDevices.first.observatoryUris.first,
|
||||
wsUri: flutterDevices.first.vmServices.first.wsAddress,
|
||||
));
|
||||
}
|
||||
|
||||
printTrace('Application running.');
|
||||
|
||||
if (vmServices != null && vmServices.isNotEmpty) {
|
||||
device.getLogReader(app: package).appPid = vmServices[0].vm.pid;
|
||||
await refreshViews();
|
||||
printTrace('Connected to $currentView.');
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.vmServices == null)
|
||||
continue;
|
||||
device.initLogReader();
|
||||
await device.refreshViews();
|
||||
printTrace('Connected to ${device.device.name}');
|
||||
}
|
||||
|
||||
if (vmServices != null && vmServices.isNotEmpty && traceStartup) {
|
||||
printStatus('Downloading startup trace info...');
|
||||
try {
|
||||
await downloadStartupTrace(vmServices[0]);
|
||||
} catch(error) {
|
||||
printError(error);
|
||||
return 2;
|
||||
if (traceStartup) {
|
||||
// Only trace startup for the first device.
|
||||
final FlutterDevice device = flutterDevices.first;
|
||||
if (device.vmServices != null && device.vmServices.isNotEmpty) {
|
||||
printStatus('Downloading startup trace info for ${device.device.name}');
|
||||
try {
|
||||
await downloadStartupTrace(device.vmServices.first);
|
||||
} catch (error) {
|
||||
printError(error);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
appFinished();
|
||||
} else if (stayResident) {
|
||||
@ -158,8 +123,13 @@ class ColdRunner extends ResidentRunner {
|
||||
@override
|
||||
void printHelp({ @required bool details }) {
|
||||
bool haveDetails = false;
|
||||
if (_result.hasObservatory)
|
||||
printStatus('The Observatory debugger and profiler is available at: ${_result.observatoryUri}');
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final String dname = device.device.name;
|
||||
if (device.observatoryUris != null) {
|
||||
for (Uri uri in device.observatoryUris)
|
||||
printStatus('An Observatory debugger and profiler on $dname is available at $uri');
|
||||
}
|
||||
}
|
||||
if (supportsServiceProtocol) {
|
||||
haveDetails = true;
|
||||
if (details)
|
||||
@ -174,8 +144,10 @@ class ColdRunner extends ResidentRunner {
|
||||
|
||||
@override
|
||||
Future<Null> preStop() async {
|
||||
// If we're running in release mode, stop the app using the device logic.
|
||||
if (vmServices == null || vmServices.isEmpty)
|
||||
await device.stopApp(package);
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
// If we're running in release mode, stop the app using the device logic.
|
||||
if (device.vmServices == null || device.vmServices.isEmpty)
|
||||
await device.device.stopApp(device.package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'application_package.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
@ -33,7 +32,7 @@ const bool kHotReloadDefault = true;
|
||||
|
||||
class HotRunner extends ResidentRunner {
|
||||
HotRunner(
|
||||
Device device, {
|
||||
List<FlutterDevice> devices, {
|
||||
String target,
|
||||
DebuggingOptions debuggingOptions,
|
||||
bool usesTerminalUI: true,
|
||||
@ -44,7 +43,7 @@ class HotRunner extends ResidentRunner {
|
||||
String packagesFilePath,
|
||||
String projectAssets,
|
||||
bool stayResident: true,
|
||||
}) : super(device,
|
||||
}) : super(devices,
|
||||
target: target,
|
||||
debuggingOptions: debuggingOptions,
|
||||
usesTerminalUI: usesTerminalUI,
|
||||
@ -54,9 +53,7 @@ class HotRunner extends ResidentRunner {
|
||||
stayResident: stayResident);
|
||||
|
||||
final String applicationBinary;
|
||||
bool get prebuiltMode => applicationBinary != null;
|
||||
Set<String> _dartDependencies;
|
||||
Uri _observatoryUri;
|
||||
|
||||
final bool benchmarkMode;
|
||||
final Map<String, int> benchmarkData = <String, int>{};
|
||||
@ -87,30 +84,30 @@ class HotRunner extends ResidentRunner {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<int> attach(List<Uri> observatoryUris, {
|
||||
Future<int> attach({
|
||||
Completer<DebugConnectionInfo> connectionInfoCompleter,
|
||||
Completer<Null> appStartedCompleter,
|
||||
String isolateFilter,
|
||||
String viewFilter,
|
||||
}) async {
|
||||
_observatoryUri = observatoryUris[0];
|
||||
try {
|
||||
await connectToServiceProtocol(
|
||||
observatoryUris, isolateFilter: isolateFilter);
|
||||
await connectToServiceProtocol(viewFilter: viewFilter);
|
||||
} catch (error) {
|
||||
printError('Error connecting to the service protocol: $error');
|
||||
return 2;
|
||||
}
|
||||
|
||||
device.getLogReader(app: package).appPid = vmServices[0].vm.pid;
|
||||
for (FlutterDevice device in flutterDevices)
|
||||
device.initLogReader();
|
||||
|
||||
try {
|
||||
final Uri baseUri = await _initDevFS();
|
||||
final List<Uri> baseUris = await _initDevFS();
|
||||
if (connectionInfoCompleter != null) {
|
||||
// Only handle one debugger connection.
|
||||
connectionInfoCompleter.complete(
|
||||
new DebugConnectionInfo(
|
||||
httpUri: _observatoryUri,
|
||||
wsUri: vmServices[0].wsAddress,
|
||||
baseUri: baseUri.toString()
|
||||
httpUri: flutterDevices.first.observatoryUris.first,
|
||||
wsUri: flutterDevices.first.vmServices.first.wsAddress,
|
||||
baseUri: baseUris.first.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -124,8 +121,10 @@ class HotRunner extends ResidentRunner {
|
||||
}
|
||||
|
||||
await refreshViews();
|
||||
for (FlutterView view in currentViews)
|
||||
printTrace('Connected to $view.');
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views)
|
||||
printTrace('Connected to $view.');
|
||||
}
|
||||
|
||||
if (stayResident) {
|
||||
setupTerminal();
|
||||
@ -174,55 +173,27 @@ class HotRunner extends ResidentRunner {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final String modeName = getModeName(debuggingOptions.buildMode);
|
||||
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
|
||||
|
||||
final TargetPlatform targetPlatform = await device.targetPlatform;
|
||||
package = getApplicationPackageForPlatform(targetPlatform, applicationBinary: applicationBinary);
|
||||
|
||||
if (package == null) {
|
||||
String message = 'No application found for $targetPlatform.';
|
||||
final String hint = getMissingPackageHintForPlatform(targetPlatform);
|
||||
if (hint != null)
|
||||
message += '\n$hint';
|
||||
printError(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Determine the Dart dependencies eagerly.
|
||||
if (!_refreshDartDependencies()) {
|
||||
// Some kind of source level error or missing file in the Dart code.
|
||||
return 1;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> platformArgs = <String, dynamic>{};
|
||||
|
||||
await startEchoingDeviceLog(package);
|
||||
|
||||
// Start the application.
|
||||
final Future<LaunchResult> futureResult = device.startApp(
|
||||
package,
|
||||
debuggingOptions.buildMode,
|
||||
mainPath: mainPath,
|
||||
debuggingOptions: debuggingOptions,
|
||||
platformArgs: platformArgs,
|
||||
route: route,
|
||||
prebuiltApplication: prebuiltMode,
|
||||
kernelPath: kernelFilePath,
|
||||
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies()
|
||||
);
|
||||
|
||||
final LaunchResult result = await futureResult;
|
||||
|
||||
if (!result.started) {
|
||||
printError('Error launching application on ${device.name}.');
|
||||
await stopEchoingDeviceLog();
|
||||
return 2;
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final int result = await device.runHot(
|
||||
hotRunner: this,
|
||||
route: route,
|
||||
shouldBuild: shouldBuild,
|
||||
);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return attach(<Uri>[result.observatoryUri],
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
appStartedCompleter: appStartedCompleter);
|
||||
return attach(
|
||||
connectionInfoCompleter: connectionInfoCompleter,
|
||||
appStartedCompleter: appStartedCompleter
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -238,15 +209,18 @@ class HotRunner extends ResidentRunner {
|
||||
}
|
||||
}
|
||||
|
||||
DevFS _devFS;
|
||||
|
||||
Future<Uri> _initDevFS() {
|
||||
Future<List<Uri>> _initDevFS() async {
|
||||
final String fsName = fs.path.basename(projectRootPath);
|
||||
_devFS = new DevFS(vmServices[0],
|
||||
fsName,
|
||||
fs.directory(projectRootPath),
|
||||
packagesFilePath: packagesFilePath);
|
||||
return _devFS.create();
|
||||
final List<Uri> devFSUris = <Uri>[];
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final Uri uri = await device.setupDevFS(
|
||||
fsName,
|
||||
fs.directory(projectRootPath),
|
||||
packagesFilePath: packagesFilePath
|
||||
);
|
||||
devFSUris.add(uri);
|
||||
}
|
||||
return devFSUris;
|
||||
}
|
||||
|
||||
Future<bool> _updateDevFS({ DevFSProgressReporter progressReporter }) async {
|
||||
@ -261,67 +235,72 @@ class HotRunner extends ResidentRunner {
|
||||
if (result != 0)
|
||||
return false;
|
||||
}
|
||||
final Status devFSStatus = logger.startProgress('Syncing files to device...',
|
||||
expectSlowOperation: true);
|
||||
int bytes = 0;
|
||||
try {
|
||||
bytes = await _devFS.update(progressReporter: progressReporter,
|
||||
bundle: assetBundle,
|
||||
bundleDirty: rebuildBundle,
|
||||
fileFilter: _dartDependencies);
|
||||
} on DevFSException {
|
||||
devFSStatus.cancel();
|
||||
return false;
|
||||
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final bool result = await device.updateDevFS(
|
||||
progressReporter: progressReporter,
|
||||
bundle: assetBundle,
|
||||
bundleDirty: rebuildBundle,
|
||||
fileFilter: _dartDependencies,
|
||||
);
|
||||
if (!result)
|
||||
return false;
|
||||
}
|
||||
devFSStatus.stop();
|
||||
|
||||
if (!hotRunnerConfig.stableDartDependencies) {
|
||||
// Clear the set after the sync so they are recomputed next time.
|
||||
_dartDependencies = null;
|
||||
}
|
||||
printTrace('Synced ${getSizeAsMB(bytes)}.');
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<Null> _evictDirtyAssets() async {
|
||||
if (_devFS.assetPathsToEvict.isEmpty)
|
||||
return;
|
||||
if (currentView.uiIsolate == null)
|
||||
throw 'Application isolate not found';
|
||||
for (String assetPath in _devFS.assetPathsToEvict) {
|
||||
await currentView.uiIsolate.flutterEvictAsset(assetPath);
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.devFS.assetPathsToEvict.isEmpty)
|
||||
return;
|
||||
if (device.views.first.uiIsolate == null)
|
||||
throw 'Application isolate not found';
|
||||
for (String assetPath in device.devFS.assetPathsToEvict)
|
||||
await device.views.first.uiIsolate.flutterEvictAsset(assetPath);
|
||||
device.devFS.assetPathsToEvict.clear();
|
||||
}
|
||||
_devFS.assetPathsToEvict.clear();
|
||||
}
|
||||
|
||||
Future<Null> _cleanupDevFS() async {
|
||||
if (_devFS != null) {
|
||||
// Cleanup the devFS; don't wait indefinitely, and ignore any errors.
|
||||
await _devFS.destroy()
|
||||
.timeout(const Duration(milliseconds: 250))
|
||||
.catchError((dynamic error) {
|
||||
printTrace('$error');
|
||||
});
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
if (device.devFS != null) {
|
||||
// Cleanup the devFS; don't wait indefinitely, and ignore any errors.
|
||||
await device.devFS.destroy()
|
||||
.timeout(const Duration(milliseconds: 250))
|
||||
.catchError((dynamic error) {
|
||||
printTrace('$error');
|
||||
});
|
||||
}
|
||||
device.devFS = null;
|
||||
}
|
||||
_devFS = null;
|
||||
}
|
||||
|
||||
Future<Null> _launchInView(Uri entryUri,
|
||||
Future<Null> _launchInView(FlutterDevice device,
|
||||
Uri entryUri,
|
||||
Uri packagesUri,
|
||||
Uri assetsDirectoryUri) async {
|
||||
final FlutterView view = currentView;
|
||||
return view.runFromSource(entryUri, packagesUri, assetsDirectoryUri);
|
||||
for (FlutterView view in device.views)
|
||||
await view.runFromSource(entryUri, packagesUri, assetsDirectoryUri);
|
||||
}
|
||||
|
||||
Future<Null> _launchFromDevFS(ApplicationPackage package,
|
||||
String mainScript) async {
|
||||
Future<Null> _launchFromDevFS(String mainScript) async {
|
||||
final String entryUri = fs.path.relative(mainScript, from: projectRootPath);
|
||||
final Uri deviceEntryUri = _devFS.baseUri.resolveUri(fs.path.toUri(entryUri));
|
||||
final Uri devicePackagesUri = _devFS.baseUri.resolve('.packages');
|
||||
final Uri deviceAssetsDirectoryUri =
|
||||
_devFS.baseUri.resolveUri(fs.path.toUri(getAssetBuildDirectory()));
|
||||
await _launchInView(deviceEntryUri,
|
||||
devicePackagesUri,
|
||||
deviceAssetsDirectoryUri);
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final Uri deviceEntryUri = device.devFS.baseUri.resolveUri(
|
||||
fs.path.toUri(entryUri));
|
||||
final Uri devicePackagesUri = device.devFS.baseUri.resolve('.packages');
|
||||
final Uri deviceAssetsDirectoryUri = device.devFS.baseUri.resolveUri(
|
||||
fs.path.toUri(getAssetBuildDirectory()));
|
||||
await _launchInView(device,
|
||||
deviceEntryUri,
|
||||
devicePackagesUri,
|
||||
deviceAssetsDirectoryUri);
|
||||
}
|
||||
}
|
||||
|
||||
Future<OperationResult> _restartFromSources() async {
|
||||
@ -333,18 +312,22 @@ class HotRunner extends ResidentRunner {
|
||||
if (!updatedDevFS)
|
||||
return new OperationResult(1, 'DevFS Synchronization Failed');
|
||||
// Check if the isolate is paused and resume it.
|
||||
if (currentView?.uiIsolate != null) {
|
||||
// Reload the isolate.
|
||||
await currentView.uiIsolate.reload();
|
||||
final ServiceEvent pauseEvent = currentView.uiIsolate.pauseEvent;
|
||||
if ((pauseEvent != null) && pauseEvent.isPauseEvent) {
|
||||
// Resume the isolate so that it can be killed by the embedder.
|
||||
await currentView.uiIsolate.resume();
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views) {
|
||||
if (view.uiIsolate != null) {
|
||||
// Reload the isolate.
|
||||
await view.uiIsolate.reload();
|
||||
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
|
||||
if ((pauseEvent != null) && pauseEvent.isPauseEvent) {
|
||||
// Resume the isolate so that it can be killed by the embedder.
|
||||
await view.uiIsolate.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We are now running from source.
|
||||
_runningFromSnapshot = false;
|
||||
await _launchFromDevFS(package, mainPath);
|
||||
await _launchFromDevFS(mainPath);
|
||||
restartTimer.stop();
|
||||
printTrace('Restart performed in '
|
||||
'${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
|
||||
@ -380,7 +363,10 @@ class HotRunner extends ResidentRunner {
|
||||
@override
|
||||
Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) async {
|
||||
if (fullRestart) {
|
||||
final Status status = logger.startProgress('Performing full restart...', progressId: 'hot.restart');
|
||||
final Status status = logger.startProgress(
|
||||
'Performing full restart...',
|
||||
progressId: 'hot.restart'
|
||||
);
|
||||
try {
|
||||
final Stopwatch timer = new Stopwatch()..start();
|
||||
await _restartFromSources();
|
||||
@ -395,7 +381,10 @@ class HotRunner extends ResidentRunner {
|
||||
} else {
|
||||
final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
|
||||
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
|
||||
final Status status = logger.startProgress('$progressPrefix hot reload...', progressId: 'hot.reload');
|
||||
final Status status = logger.startProgress(
|
||||
'$progressPrefix hot reload...',
|
||||
progressId: 'hot.reload'
|
||||
);
|
||||
try {
|
||||
final Stopwatch timer = new Stopwatch()..start();
|
||||
final OperationResult result = await _reloadSources(pause: pauseAfterRestart);
|
||||
@ -414,8 +403,12 @@ class HotRunner extends ResidentRunner {
|
||||
Future<OperationResult> _reloadSources({ bool pause: false }) async {
|
||||
printTrace('Refreshing active FlutterViews before reloading.');
|
||||
await refreshViews();
|
||||
if (currentView.uiIsolate == null)
|
||||
throw 'Application isolate not found';
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views) {
|
||||
if (view.uiIsolate == null)
|
||||
throw 'Application isolate not found';
|
||||
}
|
||||
}
|
||||
// The initial launch is from a script snapshot. When we reload from source
|
||||
// on top of a script snapshot, the first reload will be a worst case reload
|
||||
// because all of the sources will end up being dirty (library paths will
|
||||
@ -448,22 +441,19 @@ class HotRunner extends ResidentRunner {
|
||||
String reloadMessage;
|
||||
try {
|
||||
final String entryPath = fs.path.relative(mainPath, from: projectRootPath);
|
||||
final Uri deviceEntryUri = _devFS.baseUri.resolveUri(fs.path.toUri(entryPath));
|
||||
final Uri devicePackagesUri = _devFS.baseUri.resolve('.packages');
|
||||
if (benchmarkMode)
|
||||
vmReloadTimer.start();
|
||||
|
||||
Map<String, dynamic> reloadReport;
|
||||
for (FlutterView view in currentViews) {
|
||||
final Map<String, dynamic> report = await view.uiIsolate.reloadSources(
|
||||
pause: pause,
|
||||
rootLibUri: deviceEntryUri,
|
||||
packagesUri: devicePackagesUri
|
||||
final List<Future<Map<String, dynamic>>> reloadReports = <Future<Map<String, dynamic>>>[];
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final List<Future<Map<String, dynamic>>> reports = device.reloadSources(
|
||||
entryPath,
|
||||
pause: pause
|
||||
);
|
||||
// Just take the first one until we think of something smart to do.
|
||||
if (reloadReport == null)
|
||||
reloadReport = report;
|
||||
reloadReports.addAll(reports);
|
||||
}
|
||||
reloadReport = (await Future.wait(reloadReports)).first;
|
||||
|
||||
if (!validateReloadReport(reloadReport)) {
|
||||
// Reload failed.
|
||||
flutterUsage.sendEvent('hot', 'reload-reject');
|
||||
@ -476,6 +466,7 @@ class HotRunner extends ResidentRunner {
|
||||
reloadMessage = '$loadedLibraryCount of $finalLibraryCount libraries';
|
||||
}
|
||||
} catch (error, st) {
|
||||
printError("Hot reload failed: $error\n$st");
|
||||
final int errorCode = error['code'];
|
||||
final String errorMessage = error['message'];
|
||||
if (errorCode == Isolate.kIsolateReloadBarred) {
|
||||
@ -498,20 +489,24 @@ class HotRunner extends ResidentRunner {
|
||||
if (benchmarkMode)
|
||||
reassembleTimer.start();
|
||||
// Reload the isolate.
|
||||
for (FlutterView view in currentViews)
|
||||
await view.uiIsolate.reload();
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views)
|
||||
await view.uiIsolate.reload();
|
||||
}
|
||||
// We are now running from source.
|
||||
_runningFromSnapshot = false;
|
||||
// Check if the isolate is paused.
|
||||
|
||||
final List<FlutterView> reassembleViews = <FlutterView>[];
|
||||
for (FlutterView view in currentViews) {
|
||||
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
|
||||
if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) {
|
||||
// Isolate is paused. Don't reassemble.
|
||||
continue;
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
for (FlutterView view in device.views) {
|
||||
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
|
||||
if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) {
|
||||
// Isolate is paused. Don't reassemble.
|
||||
continue;
|
||||
}
|
||||
reassembleViews.add(view);
|
||||
}
|
||||
reassembleViews.add(view);
|
||||
}
|
||||
if (reassembleViews.isEmpty) {
|
||||
printTrace('Skipping reassemble because all isolates are paused.');
|
||||
@ -579,7 +574,11 @@ class HotRunner extends ResidentRunner {
|
||||
ansiAlternative: '$red$fire$bold To hot reload your app on the fly, '
|
||||
'press "r". To restart the app entirely, press "R".$reset'
|
||||
);
|
||||
printStatus('The Observatory debugger and profiler is available at: $_observatoryUri');
|
||||
for (FlutterDevice device in flutterDevices) {
|
||||
final String dname = device.device.name;
|
||||
for (Uri uri in device.observatoryUris)
|
||||
printStatus('An Observatory debugger and profiler on $dname is available at: $uri');
|
||||
}
|
||||
if (details) {
|
||||
printHelpDetails();
|
||||
printStatus('To repeat this help message, press "h". To quit, press "q".');
|
||||
|
@ -154,11 +154,11 @@ abstract class FlutterCommand extends Command<Null> {
|
||||
/// Subclasses must implement this to execute the command.
|
||||
Future<Null> runCommand();
|
||||
|
||||
/// Find and return the target [Device] based upon currently connected
|
||||
/// Find and return all target [Device]s based upon currently connected
|
||||
/// devices and criteria entered by the user on the command line.
|
||||
/// If a device cannot be found that meets specified criteria,
|
||||
/// If no device can be found that meets specified criteria,
|
||||
/// then print an error message and return `null`.
|
||||
Future<Device> findTargetDevice() async {
|
||||
Future<List<Device>> findAllTargetDevices() async {
|
||||
if (!doctor.canLaunchAnything) {
|
||||
printError("Unable to locate a development device; please run 'flutter doctor' "
|
||||
"for information about installing additional components.");
|
||||
@ -171,6 +171,9 @@ abstract class FlutterCommand extends Command<Null> {
|
||||
printStatus("No devices found with name or id "
|
||||
"matching '${deviceManager.specifiedDeviceId}'");
|
||||
return null;
|
||||
} else if (devices.isEmpty && deviceManager.hasSpecifiedAllDevices) {
|
||||
printStatus("No devices found");
|
||||
return null;
|
||||
} else if (devices.isEmpty) {
|
||||
printNoConnectedDevices();
|
||||
return null;
|
||||
@ -181,20 +184,39 @@ abstract class FlutterCommand extends Command<Null> {
|
||||
if (devices.isEmpty) {
|
||||
printStatus('No supported devices connected.');
|
||||
return null;
|
||||
} else if (devices.length > 1) {
|
||||
} else if (devices.length > 1 && !deviceManager.hasSpecifiedAllDevices) {
|
||||
if (deviceManager.hasSpecifiedDeviceId) {
|
||||
printStatus("Found ${devices.length} devices with name or id matching "
|
||||
"'${deviceManager.specifiedDeviceId}':");
|
||||
} else {
|
||||
printStatus("More than one device connected; please specify a device with "
|
||||
"the '-d <deviceId>' flag.");
|
||||
"the '-d <deviceId>' flag, or use '-d all' to act on all devices.");
|
||||
devices = await deviceManager.getAllConnectedDevices().toList();
|
||||
}
|
||||
printStatus('');
|
||||
await Device.printDevices(devices);
|
||||
return null;
|
||||
}
|
||||
return devices.single;
|
||||
return devices;
|
||||
}
|
||||
|
||||
/// Find and return the target [Device] based upon currently connected
|
||||
/// devices and criteria entered by the user on the command line.
|
||||
/// If a device cannot be found that meets specified criteria,
|
||||
/// then print an error message and return `null`.
|
||||
Future<Device> findTargetDevice() async {
|
||||
List<Device> deviceList = await findAllTargetDevices();
|
||||
if (deviceList == null)
|
||||
return null;
|
||||
if (deviceList.length > 1) {
|
||||
printStatus("More than one device connected; please specify a device with "
|
||||
"the '-d <deviceId>' flag.");
|
||||
deviceList = await deviceManager.getAllConnectedDevices().toList();
|
||||
printStatus('');
|
||||
await Device.printDevices(deviceList);
|
||||
return null;
|
||||
}
|
||||
return deviceList.single;
|
||||
}
|
||||
|
||||
void printNoConnectedDevices() {
|
||||
|
@ -127,12 +127,28 @@ class MockPortScanner extends PortScanner {
|
||||
class MockDeviceManager implements DeviceManager {
|
||||
List<Device> devices = <Device>[];
|
||||
|
||||
String _specifiedDeviceId;
|
||||
|
||||
@override
|
||||
String specifiedDeviceId;
|
||||
String get specifiedDeviceId {
|
||||
if (_specifiedDeviceId == null || _specifiedDeviceId == 'all')
|
||||
return null;
|
||||
return _specifiedDeviceId;
|
||||
}
|
||||
|
||||
@override
|
||||
set specifiedDeviceId(String id) {
|
||||
_specifiedDeviceId = id;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
|
||||
|
||||
@override
|
||||
bool get hasSpecifiedAllDevices {
|
||||
return _specifiedDeviceId != null && _specifiedDeviceId == 'all';
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Device> getAllConnectedDevices() => new Stream<Device>.fromIterable(devices);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user