From 6c8d7b00ffabff20792e03675a6cf310b27fffa7 Mon Sep 17 00:00:00 2001 From: JustWe Date: Thu, 4 Jun 2020 07:14:38 +0800 Subject: [PATCH] Show unsupported devices when no supported devices are connected (#56531) --- .../lib/src/base/user_messages.dart | 3 + packages/flutter_tools/lib/src/device.dart | 7 +++ .../lib/src/runner/flutter_command.dart | 25 +++++++-- .../commands.shard/hermetic/devices_test.dart | 10 ++++ .../commands.shard/hermetic/run_test.dart | 56 +++++++++++++++++++ .../flutter_tools/test/src/fake_devices.dart | 6 +- 6 files changed, 100 insertions(+), 7 deletions(-) diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index 466696e1c27..f2eedde630e 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -237,6 +237,9 @@ class UserMessages { "matching '$deviceId'"; String get flutterNoDevicesFound => 'No devices found'; String get flutterNoSupportedDevices => 'No supported devices connected.'; + String flutterMissPlatformProjects(List unsupportedDevicesType) => + 'If you would like your app to run on ${unsupportedDevicesType.join(' or ')}, consider running `flutter create .` to generate projects for these platforms.'; + String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:'; String flutterFoundSpecifiedDevices(int count, String deviceId) => 'Found $count devices with name or id matching $deviceId:'; String get flutterSpecifyDeviceWithAllOption => diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 6c19930cd6f..59f5b25f9f4 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -557,6 +557,13 @@ abstract class Device { await descriptions(devices).forEach(globals.printStatus); } + static List devicesPlatformTypes(List devices) { + return devices + .map( + (Device d) => d.platformType.toString(), + ).toSet().toList()..sort(); + } + /// Convert the Device object to a JSON representation suitable for serialization. Future> toJson() async { final bool isLocalEmu = await isLocalEmulator; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index b1453809ff3..b6de3cc450b 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -823,11 +823,28 @@ abstract class FlutterCommand extends Command { if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) { globals.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId)); return null; - } else if (devices.isEmpty && deviceManager.hasSpecifiedAllDevices) { - globals.printStatus(userMessages.flutterNoDevicesFound); - return null; } else if (devices.isEmpty) { - globals.printStatus(userMessages.flutterNoSupportedDevices); + if (deviceManager.hasSpecifiedAllDevices) { + globals.printStatus(userMessages.flutterNoDevicesFound); + } else { + globals.printStatus(userMessages.flutterNoSupportedDevices); + } + final List unsupportedDevices = await deviceManager.getDevices(); + if (unsupportedDevices.isNotEmpty) { + final StringBuffer result = StringBuffer(); + result.writeln(userMessages.flutterFoundButUnsupportedDevices); + result.writeAll( + await Device.descriptions(unsupportedDevices) + .map((String desc) => desc) + .toList(), + '\n', + ); + result.writeln(''); + result.writeln(userMessages.flutterMissPlatformProjects( + Device.devicesPlatformTypes(unsupportedDevices), + )); + globals.printStatus(result.toString()); + } return null; } else if (devices.length > 1 && !deviceManager.hasSpecifiedAllDevices) { if (deviceManager.hasSpecifiedDeviceId) { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart index b2d1404c49c..090e0045c28 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart @@ -38,6 +38,16 @@ void main() { ProcessManager: () => MockProcessManager(), }); + testUsingContext('get devices\' platform types', () async { + final List platformTypes = Device.devicesPlatformTypes( + await deviceManager.getAllConnectedDevices(), + ); + expect(platformTypes, ['android', 'web']); + }, overrides: { + DeviceManager: () => _FakeDeviceManager(), + ProcessManager: () => MockProcessManager(), + }); + testUsingContext('Outputs parsable JSON with --machine flag', () async { final DevicesCommand command = DevicesCommand(); await createTestCommandRunner(command).run(['devices', '--machine']); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 4e6f05bbf50..59ce881b676 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -241,6 +241,62 @@ void main() { ProcessManager: () => mockProcessManager, }); + testUsingContext('shows unsupported devices when no supported devices are found', () async { + final RunCommand command = RunCommand(); + applyMocksToCommand(command); + + final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm); + when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future.value(true)); + when(mockDevice.isSupported()).thenAnswer((Invocation invocation) => true); + when(mockDevice.supportsFastStart).thenReturn(true); + when(mockDevice.id).thenReturn('mock-id'); + when(mockDevice.name).thenReturn('mock-name'); + when(mockDevice.platformType).thenReturn(PlatformType.android); + when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) => Future.value('api-14')); + + when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) { + return Future>.value([ + mockDevice, + ]); + }); + + when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + (Invocation invocation) => Future>.value([]), + ); + + try { + await createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + ]); + fail('Expect exception'); + } on ToolExit catch (e) { + expect(e.message, null); + } + + expect( + testLogger.statusText, + containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices), + ); + expect( + testLogger.statusText, + containsIgnoringWhitespace(userMessages.flutterFoundButUnsupportedDevices), + ); + expect( + testLogger.statusText, + containsIgnoringWhitespace( + userMessages.flutterMissPlatformProjects( + Device.devicesPlatformTypes([mockDevice]), + ), + ), + ); + }, overrides: { + DeviceManager: () => mockDeviceManager, + FileSystem: () => fs, + ProcessManager: () => mockProcessManager, + }); + testUsingContext('updates cache before checking for devices', () async { final RunCommand command = RunCommand(); applyMocksToCommand(command); diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index 5dd2bebcb04..e280b7bdbf1 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -11,7 +11,7 @@ import 'package:flutter_tools/src/project.dart'; /// (`Device.toJson()` and `--machine` flag for `devices` command) List fakeDevices = [ FakeDeviceJsonData( - FakeDevice('ephemeral', 'ephemeral', true), + FakeDevice('ephemeral', 'ephemeral', true, true, PlatformType.android), { 'name': 'ephemeral', 'id': 'ephemeral', @@ -56,9 +56,9 @@ List fakeDevices = [ /// Fake device to test `devices` command class FakeDevice extends Device { - FakeDevice(this.name, String id, [bool ephemeral = true, this._isSupported = true]) : super( + FakeDevice(this.name, String id, [bool ephemeral = true, this._isSupported = true, PlatformType type = PlatformType.web]) : super( id, - platformType: PlatformType.web, + platformType: type, category: Category.mobile, ephemeral: ephemeral, );