Unverified Commit 4f88ed1d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] only restrict devices based on arch + buildMode, not emulator status (#58887)

instead of restricting profile/release mode based on whether the tool thinks the device is an emulator, restrict based on the device target architecture and the requested build mode. Notably, this enables release mode on x86_64 Android emulators, but not x86 emulators since we do not support that as an AOT target.

This does not add release mode support for simulators, since this requires us to build and upload artifacts for simulator/x86_64
parent 206ccc36
......@@ -227,6 +227,20 @@ class AndroidDevice extends Device {
return _applicationPlatform;
}
@override
Future<bool> supportsRuntimeMode(BuildMode buildMode) async {
switch (await targetPlatform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
return buildMode != BuildMode.jitRelease;
case TargetPlatform.android_x86:
return buildMode == BuildMode.debug;
default:
throw UnsupportedError('Invalid target platform for Android');
}
}
@override
Future<String> get sdkNameAndVersion async =>
'Android ${await _sdkVersion} (API ${await apiVersion})';
......
......@@ -449,9 +449,13 @@ class AppDomain extends Domain {
bool ipv6 = false,
String isolateFilter,
}) async {
if (await device.isLocalEmulator && !options.buildInfo.supportsEmulator) {
throw Exception('${toTitleCase(options.buildInfo.friendlyModeName)} mode is not supported for emulators.');
if (!await device.supportsRuntimeMode(options.buildInfo.mode)) {
throw Exception(
'${toTitleCase(options.buildInfo.friendlyModeName)} '
'mode is not supported for ${device.name}.',
);
}
// We change the current working directory for the duration of the `start` command.
final Directory cwd = globals.fs.currentDirectory;
globals.fs.currentDirectory = globals.fs.directory(projectDirectory);
......
......@@ -446,35 +446,32 @@ class RunCommand extends RunCommandBase {
'channel.', null);
}
final BuildMode buildMode = getBuildMode();
for (final Device device in devices) {
if (await device.isLocalEmulator) {
if (await device.supportsHardwareRendering) {
final bool enableSoftwareRendering = boolArg('enable-software-rendering') == true;
if (enableSoftwareRendering) {
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 get graphics artifacts, '
'consider enabling software rendering with "--enable-software-rendering".'
);
}
}
if (!isEmulatorBuildMode(getBuildMode())) {
throwToolExit('${toTitleCase(getFriendlyModeName(getBuildMode()))} mode is not supported for emulators.');
}
if (!await device.supportsRuntimeMode(buildMode)) {
throwToolExit(
'${toTitleCase(getFriendlyModeName(buildMode))} '
'mode is not supported by ${device.name}.',
);
}
}
if (hotMode) {
for (final Device device in devices) {
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;
......
......@@ -62,6 +62,9 @@ abstract class DesktopDevice extends Device {
@override
Future<String> get sdkNameAndVersion async => globals.os.name;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
@override
DeviceLogReader getLogReader({
ApplicationPackage app,
......
......@@ -358,8 +358,16 @@ abstract class PollingDeviceDiscovery extends DeviceDiscovery {
String toString() => '$name device discovery';
}
/// A device is a physical hardware that can run a flutter application.
///
/// This may correspond to a connected iOS or Android device, or represent
/// the host operating system in the case of Flutter Desktop.
abstract class Device {
Device(this.id, {@required this.category, @required this.platformType, @required this.ephemeral});
Device(this.id, {
@required this.category,
@required this.platformType,
@required this.ephemeral,
});
final String id;
......@@ -377,6 +385,9 @@ abstract class Device {
bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
///
/// This may return `true` for certain physical Android devices, and is
/// generally only a best effort guess.
Future<bool> get isLocalEmulator;
/// The unique identifier for the emulator that corresponds to this device, or
......@@ -387,24 +398,16 @@ abstract class Device {
/// will be returned.
Future<String> get emulatorId;
/// Whether this device can run the provided [buildMode].
///
/// For example, some emulator architectures cannot run profile or
/// release builds.
FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => true;
/// Whether the device is a simulator on a platform which supports hardware rendering.
// This is soft-deprecated since the logic is not correct expect for iOS simulators.
Future<bool> get supportsHardwareRendering async {
assert(await isLocalEmulator);
switch (await targetPlatform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
return true;
case TargetPlatform.ios:
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia_arm64:
case TargetPlatform.fuchsia_x64:
default:
return false;
}
return true;
}
/// Whether the device is supported for the current project directory.
......
......@@ -251,6 +251,9 @@ class FuchsiaDevice extends Device {
@override
bool isSupported() => true;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
@override
Future<LaunchResult> startApp(
covariant FuchsiaApp package, {
......
......@@ -204,6 +204,9 @@ class IOSDevice extends Device {
@override
final String name;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
final DarwinArch cpuArchitecture;
final IOSDeviceInterface interfaceType;
......
......@@ -307,6 +307,12 @@ class IOSSimulator extends Device {
@override
bool get supportsHotRestart => true;
@override
Future<bool> get supportsHardwareRendering async => false;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug;
Map<ApplicationPackage, _IOSSimulatorLogReader> _logReaders;
_IOSSimulatorDevicePortForwarder _portForwarder;
......
......@@ -69,6 +69,9 @@ class FlutterTesterDevice extends Device {
return 'Flutter ${flutterVersion.frameworkRevisionShort}';
}
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
......
......@@ -70,6 +70,9 @@ abstract class ChromiumDevice extends Device {
@override
bool get supportsScreenshot => false;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
@override
void clearLogs() { }
......@@ -341,6 +344,9 @@ class WebServerDevice extends Device {
@override
bool get supportsFlutterExit => false;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
@override
Future<bool> get isLocalEmulator async => false;
......
......@@ -84,7 +84,7 @@ void main() {
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': false,
'hardwareRendering': true,
'startPaused': true
}
}
......
......@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/net.dart';
......@@ -42,7 +41,6 @@ void main() {
MockDeviceManager mockDeviceManager;
MockFlutterVersion mockStableFlutterVersion;
MockFlutterVersion mockUnstableFlutterVersion;
MockStdio mockStdio;
setUpAll(() {
Cache.disableLocking();
......@@ -52,10 +50,6 @@ void main() {
mockUnstableFlutterVersion = MockFlutterVersion(isStable: false);
});
setUp((){
mockStdio = MockStdio()..stdout.terminalColumns = 80;
});
testUsingContext('fails when target not found', () async {
final RunCommand command = RunCommand();
applyMocksToCommand(command);
......@@ -122,38 +116,6 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Fails with toolExit run in profile mode on emulator with machine flag', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').writeAsStringSync('\n');
globals.fs.file('lib/main.dart').createSync(recursive: true);
final FakeDevice device = FakeDevice(isLocalEmulator: true);
when(deviceManager.getAllConnectedDevices()).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.getDevices()).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) async {
return <Device>[device];
});
when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
when(deviceManager.deviceDiscoverers).thenReturn(<DeviceDiscovery>[]);
final RunCommand command = RunCommand();
applyMocksToCommand(command);
await expectLater(createTestCommandRunner(command).run(<String>[
'run',
'--no-pub',
'--machine',
'--profile',
]), throwsToolExit(message: 'not supported for emulators'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
DeviceManager: () => MockDeviceManager(),
Stdio: () => mockStdio,
});
testUsingContext('Walks upward looking for a pubspec.yaml and exits if missing', () async {
globals.fs.currentDirectory = globals.fs.directory(globals.fs.path.join('a', 'b', 'c'))
..createSync(recursive: true);
......@@ -344,6 +306,7 @@ void main() {
final RunCommand command = RunCommand();
applyMocksToCommand(command);
final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
when(mockDevice.supportsRuntimeMode(any)).thenAnswer((Invocation invocation) => true);
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(FakeDeviceLogReader());
when(mockDevice.supportsFastStart).thenReturn(true);
......@@ -688,6 +651,9 @@ class FakeDevice extends Fake implements Device {
@override
Future<bool> get isLocalEmulator => Future<bool>.value(_isLocalEmulator);
@override
bool supportsRuntimeMode(BuildMode mode) => true;
@override
bool get supportsHotReload => false;
......
......@@ -98,6 +98,38 @@ void main() {
}
});
testWithoutContext('AndroidDevice supports profile/release mode on arm and x64 targets '
'abi and abiList', () async {
// The format is [ABI, ABI list]: expected release mode support.
final Map<List<String>, bool> values = <List<String>, bool>{
<String>['x86_64', 'unknown']: true,
<String>['x86', 'unknown']: false,
// The default ABI is arm32
<String>['???', 'unknown']: true,
<String>['arm64-v8a', 'arm64-v8a,']: true,
// The Kindle Fire runs 32 bit apps on 64 bit hardware.
<String>['arm64-v8a', 'arm']: true,
};
for (final MapEntry<List<String>, bool> entry in values.entries) {
final AndroidDevice device = setUpAndroidDevice(
processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['adb', '-s', '1234', 'shell', 'getprop'],
stdout: '[ro.product.cpu.abi]: [${entry.key.first}]\n'
'[ro.product.cpu.abilist]: [${entry.key.last}]'
)
]),
);
expect(await device.supportsRuntimeMode(BuildMode.release), entry.value);
// Debug is always supported.
expect(await device.supportsRuntimeMode(BuildMode.debug), true);
// jitRelease is never supported.
expect(await device.supportsRuntimeMode(BuildMode.jitRelease), false);
}
});
testWithoutContext('AndroidDevice can detect local emulator for known types', () async {
final Set<String> knownPhyiscal = <String>{
'qcom',
......@@ -162,7 +194,6 @@ void main() {
);
expect(await device.isLocalEmulator, true);
expect(await device.supportsHardwareRendering, true);
});
testWithoutContext('isSupportedForProject is true on module project', () async {
......
......@@ -76,6 +76,17 @@ void main() {
expect(device.name, name);
});
testWithoutContext('supports all runtime modes besides jitRelease', () {
const String deviceId = 'e80::0000:a00a:f00f:2002/3';
const String name = 'halfbaked';
final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('lists nothing when workflow cannot list devices', () async {
final MockFuchsiaWorkflow fuchsiaWorkflow = MockFuchsiaWorkflow();
final FuchsiaDevices fuchsiaDevices = FuchsiaDevices(
......
......@@ -145,6 +145,27 @@ void main() {
).majorSdkVersion, 0);
});
testWithoutContext('Supports debug, profile, and release modes', () {
final IOSDevice device = IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
iMobileDevice: iMobileDevice,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
interfaceType: IOSDeviceInterface.usb,
);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
for (final Platform platform in unsupportedPlatforms) {
testWithoutContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
expect(
......
......@@ -83,6 +83,23 @@ void main() {
}, testOn: 'posix');
});
testUsingContext('simulators only support debug mode', () async {
final IOSSimulator simulator = IOSSimulator(
'123',
simControl: MockSimControl(),
xcode: MockXcode(),
);
expect(simulator.supportsRuntimeMode(BuildMode.debug), true);
expect(simulator.supportsRuntimeMode(BuildMode.profile), false);
expect(simulator.supportsRuntimeMode(BuildMode.release), false);
expect(simulator.supportsRuntimeMode(BuildMode.jitRelease), false);
}, overrides: <Type, Generator>{
Platform: () => osx,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
group('logFilePath', () {
MockSimControl mockSimControl;
MockXcode mockXcode;
......
......@@ -35,6 +35,11 @@ void main() {
expect(await device.isAppInstalled(linuxApp), true);
expect(await device.stopApp(linuxApp), true);
expect(device.category, Category.desktop);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('LinuxDevice: no devices listed if platform unsupported', () async {
......
......@@ -46,6 +46,11 @@ void main() {
expect(await device.isLatestBuildInstalled(mockMacOSApp), true);
expect(await device.isAppInstalled(mockMacOSApp), true);
expect(device.category, Category.desktop);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testUsingContext('No devices listed if platform unsupported', () async {
......
......@@ -5,6 +5,7 @@
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/web/chrome.dart';
import 'package:flutter_tools/src/web/web_device.dart';
......@@ -50,6 +51,11 @@ void main() {
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await chromeDevice.portForwarder.forward(1), 1);
expect(chromeDevice.supportsRuntimeMode(BuildMode.debug), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.profile), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.release), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('MicrosoftEdge defaults', () async {
......@@ -70,6 +76,11 @@ void main() {
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await chromeDevice.portForwarder.forward(1), 1);
expect(chromeDevice.supportsRuntimeMode(BuildMode.debug), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.profile), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.release), true);
expect(chromeDevice.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('Server defaults', () async {
......@@ -88,7 +99,12 @@ void main() {
expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
expect(await device.portForwarder.forward(1), 1);
});
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('Chrome device is listed when Chrome can be run', () async {
final WebDevices webDevices = WebDevices(
......
......@@ -38,6 +38,11 @@ void main() {
expect(await device.isLatestBuildInstalled(windowsApp), true);
expect(await device.isAppInstalled(windowsApp), true);
expect(device.category, Category.desktop);
expect(device.supportsRuntimeMode(BuildMode.debug), true);
expect(device.supportsRuntimeMode(BuildMode.profile), true);
expect(device.supportsRuntimeMode(BuildMode.release), true);
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testUsingContext('No devices listed if platform unsupported', () async {
......
......@@ -47,7 +47,7 @@ List<FakeDeviceJsonData> fakeDevices = <FakeDeviceJsonData>[
'screenshot': false,
'fastStart': false,
'flutterExit': true,
'hardwareRendering': false,
'hardwareRendering': true,
'startPaused': true
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment