Unverified Commit b460d0ee authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate emulators to null safety (#92738)

parent 9bff08b7
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'package:meta/meta.dart';
......@@ -23,11 +21,11 @@ import 'android_sdk.dart';
class AndroidEmulators extends EmulatorDiscovery {
AndroidEmulators({
@required AndroidSdk androidSdk,
@required AndroidWorkflow androidWorkflow,
@required FileSystem fileSystem,
@required Logger logger,
@required ProcessManager processManager,
AndroidSdk? androidSdk,
required AndroidWorkflow androidWorkflow,
required FileSystem fileSystem,
required Logger logger,
required ProcessManager processManager,
}) : _androidSdk = androidSdk,
_androidWorkflow = androidWorkflow,
_fileSystem = fileSystem,
......@@ -36,7 +34,7 @@ class AndroidEmulators extends EmulatorDiscovery {
_processUtils = ProcessUtils(logger: logger, processManager: processManager);
final AndroidWorkflow _androidWorkflow;
final AndroidSdk _androidSdk;
final AndroidSdk? _androidSdk;
final FileSystem _fileSystem;
final Logger _logger;
final ProcessManager _processManager;
......@@ -50,14 +48,14 @@ class AndroidEmulators extends EmulatorDiscovery {
@override
bool get canLaunchAnything => _androidWorkflow.canListEmulators
&& _androidSdk.getAvdManagerPath() != null;
&& _androidSdk?.getAvdManagerPath() != null;
@override
Future<List<Emulator>> get emulators => _getEmulatorAvds();
/// Return the list of available emulator AVDs.
Future<List<AndroidEmulator>> _getEmulatorAvds() async {
final String emulatorPath = _androidSdk?.emulatorPath;
final String? emulatorPath = _androidSdk?.emulatorPath;
if (emulatorPath == null) {
return <AndroidEmulator>[];
}
......@@ -82,7 +80,7 @@ class AndroidEmulators extends EmulatorDiscovery {
AndroidEmulator _loadEmulatorInfo(String id) {
id = id.trim();
final String avdPath = _androidSdk.getAvdPath();
final String? avdPath = _androidSdk?.getAvdPath();
final AndroidEmulator androidEmulatorWithoutProperties = AndroidEmulator(
id,
processManager: _processManager,
......@@ -97,10 +95,11 @@ class AndroidEmulators extends EmulatorDiscovery {
return androidEmulatorWithoutProperties;
}
final Map<String, String> ini = parseIniLines(iniFile.readAsLinesSync());
if (ini['path'] == null) {
final String? path = ini['path'];
if (path == null) {
return androidEmulatorWithoutProperties;
}
final File configFile = _fileSystem.file(_fileSystem.path.join(ini['path'], 'config.ini'));
final File configFile = _fileSystem.file(_fileSystem.path.join(path, 'config.ini'));
if (!configFile.existsSync()) {
return androidEmulatorWithoutProperties;
}
......@@ -117,20 +116,20 @@ class AndroidEmulators extends EmulatorDiscovery {
class AndroidEmulator extends Emulator {
AndroidEmulator(String id, {
Map<String, String> properties,
@required Logger logger,
@required AndroidSdk androidSdk,
@required ProcessManager processManager,
Map<String, String>? properties,
required Logger logger,
AndroidSdk? androidSdk,
required ProcessManager processManager,
}) : _properties = properties,
_logger = logger,
_androidSdk = androidSdk,
_processUtils = ProcessUtils(logger: logger, processManager: processManager),
super(id, properties != null && properties.isNotEmpty);
final Map<String, String> _properties;
final Map<String, String>? _properties;
final Logger _logger;
final ProcessUtils _processUtils;
final AndroidSdk _androidSdk;
final AndroidSdk? _androidSdk;
// Android Studio uses the ID with underscores replaced with spaces
// for the name if displayname is not set so we do the same.
......@@ -138,7 +137,7 @@ class AndroidEmulator extends Emulator {
String get name => _prop('avd.ini.displayname') ?? id.replaceAll('_', ' ').trim();
@override
String get manufacturer => _prop('hw.device.manufacturer');
String? get manufacturer => _prop('hw.device.manufacturer');
@override
Category get category => Category.mobile;
......@@ -146,12 +145,16 @@ class AndroidEmulator extends Emulator {
@override
PlatformType get platformType => PlatformType.android;
String _prop(String name) => _properties != null ? _properties[name] : null;
String? _prop(String name) => _properties != null ? _properties![name] : null;
@override
Future<void> launch({@visibleForTesting Duration startupDuration, bool coldBoot = false}) async {
Future<void> launch({@visibleForTesting Duration? startupDuration, bool coldBoot = false}) async {
final String? emulatorPath = _androidSdk?.emulatorPath;
if (emulatorPath == null) {
throw Exception('Emulator is missing from the Android SDK');
}
final List<String> command = <String>[
_androidSdk.emulatorPath,
emulatorPath,
'-avd',
id,
if (coldBoot)
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:math' as math;
import 'package:meta/meta.dart';
......@@ -19,16 +17,16 @@ import 'base/process.dart';
import 'device.dart';
import 'ios/ios_emulators.dart';
EmulatorManager get emulatorManager => context.get<EmulatorManager>();
EmulatorManager? get emulatorManager => context.get<EmulatorManager>();
/// A class to get all available emulators.
class EmulatorManager {
EmulatorManager({
@required AndroidSdk androidSdk,
@required Logger logger,
@required ProcessManager processManager,
@required AndroidWorkflow androidWorkflow,
@required FileSystem fileSystem,
AndroidSdk? androidSdk,
required Logger logger,
required ProcessManager processManager,
required AndroidWorkflow androidWorkflow,
required FileSystem fileSystem,
}) : _androidSdk = androidSdk,
_processUtils = ProcessUtils(logger: logger, processManager: processManager),
_androidEmulators = AndroidEmulators(
......@@ -41,7 +39,7 @@ class EmulatorManager {
_emulatorDiscoverers.add(_androidEmulators);
}
final AndroidSdk _androidSdk;
final AndroidSdk? _androidSdk;
final AndroidEmulators _androidEmulators;
final ProcessUtils _processUtils;
......@@ -55,14 +53,19 @@ class EmulatorManager {
final List<Emulator> emulators = await getAllAvailableEmulators();
searchText = searchText.toLowerCase();
bool exactlyMatchesEmulatorId(Emulator emulator) =>
emulator.id?.toLowerCase() == searchText ||
emulator.name?.toLowerCase() == searchText;
emulator.id.toLowerCase() == searchText ||
emulator.name.toLowerCase() == searchText;
bool startsWithEmulatorId(Emulator emulator) =>
emulator.id?.toLowerCase()?.startsWith(searchText) == true ||
emulator.name?.toLowerCase()?.startsWith(searchText) == true;
final Emulator exactMatch =
emulators.firstWhere(exactlyMatchesEmulatorId, orElse: () => null);
emulator.id.toLowerCase().startsWith(searchText) == true ||
emulator.name.toLowerCase().startsWith(searchText) == true;
Emulator? exactMatch;
for (final Emulator emulator in emulators) {
if (exactlyMatchesEmulatorId(emulator)) {
exactMatch = emulator;
break;
}
}
if (exactMatch != null) {
return <Emulator>[exactMatch];
}
......@@ -85,7 +88,7 @@ class EmulatorManager {
}
/// Return the list of all available emulators.
Future<CreateEmulatorResult> createEmulator({ String name }) async {
Future<CreateEmulatorResult> createEmulator({ String? name }) async {
if (name == null || name.isEmpty) {
const String autoName = 'flutter_emulator';
// Don't use getEmulatorsMatching here, as it will only return one
......@@ -102,21 +105,23 @@ class EmulatorManager {
name = '${autoName}_${++suffix}';
}
}
if (!_androidEmulators.canLaunchAnything) {
return CreateEmulatorResult(name,
final String emulatorName = name!;
final String? avdManagerPath = _androidSdk?.avdManagerPath;
if (avdManagerPath == null || !_androidEmulators.canLaunchAnything) {
return CreateEmulatorResult(emulatorName,
success: false, error: 'avdmanager is missing from the Android SDK'
);
}
final String device = await _getPreferredAvailableDevice();
final String? device = await _getPreferredAvailableDevice(avdManagerPath);
if (device == null) {
return CreateEmulatorResult(name,
return CreateEmulatorResult(emulatorName,
success: false, error: 'No device definitions are available');
}
final String sdkId = await _getPreferredSdkId();
final String? sdkId = await _getPreferredSdkId(avdManagerPath);
if (sdkId == null) {
return CreateEmulatorResult(name,
return CreateEmulatorResult(emulatorName,
success: false,
error:
'No suitable Android AVD system images are available. You may need to install these'
......@@ -128,7 +133,7 @@ class EmulatorManager {
// to flutter users. Specifically:
// - Removes lines that say "null" (!)
// - Removes lines that tell the user to use '--force' to overwrite emulators
String cleanError(String error) {
String? cleanError(String? error) {
if (error == null || error.trim() == '') {
return null;
}
......@@ -141,16 +146,16 @@ class EmulatorManager {
.trim();
}
final RunResult runResult = await _processUtils.run(<String>[
_androidSdk?.avdManagerPath,
avdManagerPath,
'create',
'avd',
'-n', name,
'-n', emulatorName,
'-k', sdkId,
'-d', device,
], environment: _androidSdk?.sdkManagerEnv,
);
return CreateEmulatorResult(
name,
emulatorName,
success: runResult.exitCode == 0,
output: runResult.stdout,
error: cleanError(runResult.stderr),
......@@ -162,9 +167,9 @@ class EmulatorManager {
'pixel_xl',
];
Future<String> _getPreferredAvailableDevice() async {
Future<String?> _getPreferredAvailableDevice(String avdManagerPath) async {
final List<String> args = <String>[
_androidSdk?.avdManagerPath,
avdManagerPath,
'list',
'device',
'-c',
......@@ -180,19 +185,21 @@ class EmulatorManager {
.where((String l) => preferredDevices.contains(l.trim()))
.toList();
return preferredDevices.firstWhere(
(String d) => availableDevices.contains(d),
orElse: () => null,
);
for (final String device in preferredDevices) {
if (availableDevices.contains(device)) {
return device;
}
}
return null;
}
static final RegExp _androidApiVersion = RegExp(r';android-(\d+);');
Future<String> _getPreferredSdkId() async {
Future<String?> _getPreferredSdkId(String avdManagerPath) async {
// It seems that to get the available list of images, we need to send a
// request to create without the image and it'll provide us a list :-(
final List<String> args = <String>[
_androidSdk?.avdManagerPath,
avdManagerPath,
'create',
'avd',
'-n', 'temp',
......@@ -209,7 +216,7 @@ class EmulatorManager {
.toList();
final List<int> availableApiVersions = availableIDs
.map<String>((String id) => _androidApiVersion.firstMatch(id).group(1))
.map<String>((String id) => _androidApiVersion.firstMatch(id)!.group(1)!)
.map<int>((String apiVersion) => int.parse(apiVersion))
.toList();
......@@ -220,10 +227,12 @@ class EmulatorManager {
// We're out of preferences, we just have to return the first one with the high
// API version.
return availableIDs.firstWhere(
(String id) => id.contains(';android-$apiVersion;'),
orElse: () => null,
);
for (final String id in availableIDs) {
if (id.contains(';android-$apiVersion;')) {
return id;
}
}
return null;
}
/// Whether we're capable of listing any emulators given the current environment configuration.
......@@ -252,7 +261,7 @@ abstract class Emulator {
final String id;
final bool hasConfig;
String get name;
String get manufacturer;
String? get manufacturer;
Category get category;
PlatformType get platformType;
......@@ -282,10 +291,10 @@ abstract class Emulator {
final List<List<String>> table = <List<String>>[
for (final Emulator emulator in emulators)
<String>[
emulator.id ?? '',
emulator.name ?? '',
emulator.id,
emulator.name,
emulator.manufacturer ?? '',
emulator.platformType?.toString() ?? '',
emulator.platformType.toString(),
],
];
......@@ -315,10 +324,10 @@ abstract class Emulator {
}
class CreateEmulatorResult {
CreateEmulatorResult(this.emulatorName, {this.success, this.output, this.error});
CreateEmulatorResult(this.emulatorName, {required this.success, this.output, this.error});
final bool success;
final String emulatorName;
final String output;
final String error;
final String? output;
final String? error;
}
......@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import '../base/common.dart';
import '../base/process.dart';
import '../device.dart';
import '../emulator.dart';
......@@ -15,7 +14,7 @@ class IOSEmulators extends EmulatorDiscovery {
bool get supportsPlatform => globals.platform.isMacOS;
@override
bool get canListAnything => globals.iosWorkflow.canListEmulators;
bool get canListAnything => globals.iosWorkflow?.canListEmulators == true;
@override
Future<List<Emulator>> get emulators async => getEmulators();
......@@ -41,12 +40,16 @@ class IOSEmulator extends Emulator {
@override
Future<void> launch({bool coldBoot = false}) async {
final String? simulatorPath = globals.xcode?.getSimulatorPath();
if (simulatorPath == null) {
throwToolExit('Could not find Simulator app');
}
Future<bool> launchSimulator(List<String> additionalArgs) async {
final List<String> args = <String>[
'open',
...additionalArgs,
'-a',
globals.xcode.getSimulatorPath(),
simulatorPath,
];
final RunResult launchResult = await globals.processUtils.run(args);
......@@ -70,7 +73,7 @@ class IOSEmulator extends Emulator {
/// Return the list of iOS Simulators (there can only be zero or one).
List<IOSEmulator> getEmulators() {
final String simulatorPath = globals.xcode.getSimulatorPath();
final String? simulatorPath = globals.xcode?.getSimulatorPath();
if (simulatorPath == null) {
return <IOSEmulator>[];
}
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'package:flutter_tools/src/android/android_emulator.dart';
......@@ -125,7 +123,7 @@ void main() {
});
group('Android emulator launch ', () {
FakeAndroidSdk mockSdk;
late FakeAndroidSdk mockSdk;
setUp(() {
mockSdk = FakeAndroidSdk();
......@@ -198,10 +196,30 @@ void main() {
expect(logger.errorText, isEmpty);
});
testWithoutContext('throws if emulator not found', () async {
mockSdk.emulatorPath = null;
final AndroidEmulator emulator = AndroidEmulator(
emulatorID,
processManager: FakeProcessManager.empty(),
androidSdk: mockSdk,
logger: BufferLogger.test(),
);
await expectLater(
() => emulator.launch(startupDuration: Duration.zero),
throwsA(isException.having(
(Exception exception) => exception.toString(),
'description',
contains('Emulator is missing from the Android SDK'),
)),
);
});
});
}
class FakeAndroidSdk extends Fake implements AndroidSdk {
@override
String emulatorPath;
String? emulatorPath;
}
......@@ -97,7 +97,6 @@ void main() {
stdout: 'existing-avd-1',
),
]),
androidSdk: null,
androidWorkflow: AndroidWorkflow(
androidSdk: sdk,
featureFlags: TestFeatureFlags(),
......
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