Add --create option to `flutter emulators` command (#18235)
* Add --create option to flutter emulators
* Tweaks to error message
* Simplify emulator search logic
* Make name optional
* Add a note about this option being used with --create
* Tweaks to help information
* Switch to processManager for easier testing
* Don't crash on missing files or missing properties in Android Emulator
* Move name suffixing into emulator manager
This allows it to be tested in the EmulatorManager tests and also used by daemon later if desired.
* Pass the context's android SDK through so it can be mocked by tests
* Misc fixes
* Add tests around emulator creation
Process calls are mocked to avoid needing a real SDK (and to be fast). Full integration tests may be useful, but may require ensuring all build environments etc. are set up correctly.
* Simplify avdManagerPath
Previous changes were to emulatorPath!
* Fix lint errors
* Fix incorrect file exgtension for Windows
* Fix an issue where no system images would crash
reduce throws on an empty collection.
* Fix "null" appearing in error messages
The name we attempted to use will now always be returned, even in the case of failure.
* Add additional info to missing-system-image failure message
On Windows after installing Andriod Studio I didn't have any of these and got this message. Installing with sdkmanager fixed the issue.
* Fix thrown errors
runResult had a toString() but we moved to ProcessResult when switching to ProcessManager to this ended up throwing "Instance of ProcessResult".
* Fix package import
* Fix more package imports
* Move mock implementation into Mock class
There seemed to be issues using Lists in args with Mockito that I couldn't figure out (docs say to use typed() but I couldn't make this compile with these lists still)..
* Rename method that's ambigious now we have create
* Handle where there's no avd path
* Add another toList() :(
* Remove comment that was rewritten
* Fix forbidden import
* Make optional arg more obviously optional
* Reformat doc
* Note that we create a pixel device in help text
* Make this a named arg
diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart
index adb1ee7..582bf0f 100644
--- a/packages/flutter_tools/lib/src/android/android_emulator.dart
+++ b/packages/flutter_tools/lib/src/android/android_emulator.dart
@@ -9,7 +9,8 @@
import '../android/android_sdk.dart';
import '../android/android_workflow.dart';
import '../base/file_system.dart';
-import '../base/process.dart';
+import '../base/io.dart';
+import '../base/process_manager.dart';
import '../emulator.dart';
import 'android_sdk.dart';
@@ -31,21 +32,23 @@
Map<String, String> _properties;
@override
- String get name => _properties['hw.device.name'];
+ String get name => _prop('hw.device.name');
@override
- String get manufacturer => _properties['hw.device.manufacturer'];
+ String get manufacturer => _prop('hw.device.manufacturer');
@override
String get label => _properties['avd.ini.displayname'];
+ String _prop(String name) => _properties != null ? _properties[name] : null;
+
@override
Future<void> launch() async {
final Future<void> launchResult =
- runAsync(<String>[getEmulatorPath(), '-avd', id])
- .then((RunResult runResult) {
+ processManager.run(<String>[getEmulatorPath(), '-avd', id])
+ .then((ProcessResult runResult) {
if (runResult.exitCode != 0) {
- throw '$runResult';
+ throw '${runResult.stdout}\n${runResult.stderr}'.trimRight();
}
});
// emulator continues running on a successful launch so if we
@@ -65,10 +68,12 @@
return <AndroidEmulator>[];
}
- final String listAvdsOutput = runSync(<String>[emulatorPath, '-list-avds']);
+ final String listAvdsOutput = processManager.runSync(<String>[emulatorPath, '-list-avds']).stdout;
final List<AndroidEmulator> emulators = <AndroidEmulator>[];
- extractEmulatorAvdInfo(listAvdsOutput, emulators);
+ if (listAvdsOutput != null) {
+ extractEmulatorAvdInfo(listAvdsOutput, emulators);
+ }
return emulators;
}
@@ -76,21 +81,26 @@
/// of emulators by reading information from the relevant ini files.
void extractEmulatorAvdInfo(String text, List<AndroidEmulator> emulators) {
for (String id in text.trim().split('\n').where((String l) => l != '')) {
- emulators.add(_createEmulator(id));
+ emulators.add(_loadEmulatorInfo(id));
}
}
-AndroidEmulator _createEmulator(String id) {
+AndroidEmulator _loadEmulatorInfo(String id) {
id = id.trim();
- final File iniFile = fs.file(fs.path.join(getAvdPath(), '$id.ini'));
- final Map<String, String> ini = parseIniLines(iniFile.readAsLinesSync());
-
- if (ini['path'] != null) {
- final File configFile = fs.file(fs.path.join(ini['path'], 'config.ini'));
- if (configFile.existsSync()) {
- final Map<String, String> properties =
- parseIniLines(configFile.readAsLinesSync());
- return new AndroidEmulator(id, properties);
+ final String avdPath = getAvdPath();
+ if (avdPath != null) {
+ final File iniFile = fs.file(fs.path.join(avdPath, '$id.ini'));
+ if (iniFile.existsSync()) {
+ final Map<String, String> ini = parseIniLines(iniFile.readAsLinesSync());
+ if (ini['path'] != null) {
+ final File configFile =
+ fs.file(fs.path.join(ini['path'], 'config.ini'));
+ if (configFile.existsSync()) {
+ final Map<String, String> properties =
+ parseIniLines(configFile.readAsLinesSync());
+ return new AndroidEmulator(id, properties);
+ }
+ }
}
}
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index 79a5d67..2dcc5bf 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -64,16 +64,8 @@
/// will work for those users who have Android Tools installed but
/// not the full SDK.
String getEmulatorPath([AndroidSdk existingSdk]) {
- if (existingSdk?.emulatorPath != null)
- return existingSdk.emulatorPath;
-
- final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
-
- if (sdk?.latestVersion == null) {
- return os.which('emulator')?.path;
- } else {
- return sdk.emulatorPath;
- }
+ return existingSdk?.emulatorPath ??
+ AndroidSdk.locateAndroidSdk()?.emulatorPath;
}
/// Locate the path for storing AVD emulator images. Returns null if none found.
@@ -104,6 +96,15 @@
);
}
+/// Locate 'avdmanager'. Prefer to use one from an Android SDK, if we can locate that.
+/// This should be used over accessing androidSdk.avdManagerPath directly because it
+/// will work for those users who have Android Tools installed but
+/// not the full SDK.
+String getAvdManagerPath([AndroidSdk existingSdk]) {
+ return existingSdk?.avdManagerPath ??
+ AndroidSdk.locateAndroidSdk()?.avdManagerPath;
+}
+
class AndroidNdkSearchError {
AndroidNdkSearchError(this.reason);
@@ -314,6 +315,8 @@
String get emulatorPath => getEmulatorPath();
+ String get avdManagerPath => getAvdManagerPath();
+
/// Validate the Android SDK. This returns an empty list if there are no
/// issues; otherwise, it returns a list of issues found.
List<String> validateSdkWellFormed() {
@@ -343,6 +346,14 @@
return null;
}
+ String getAvdManagerPath() {
+ final String binaryName = platform.isWindows ? 'avdmanager.bat' : 'avdmanager';
+ final String path = fs.path.join(directory, 'tools', 'bin', binaryName);
+ if (fs.file(path).existsSync())
+ return path;
+ return null;
+ }
+
void _init() {
Iterable<Directory> platforms = <Directory>[]; // android-22, ...