Add basic support for listing Android AVDs

Very basic support for "flutter emulators" which just lists the available Android AVDs.

Relates to:

https://github.com/flutter/flutter/issues/14822
https://github.com/Dart-Code/Dart-Code/issues/490
https://github.com/flutter/flutter/issues/13379
This commit is contained in:
Danny Tuppeny 2018-04-18 10:48:39 +01:00 committed by Danny Tuppeny
parent 21c2e47f29
commit 53840fb0ce
9 changed files with 324 additions and 0 deletions

View File

@ -15,6 +15,7 @@ import 'src/commands/daemon.dart';
import 'src/commands/devices.dart';
import 'src/commands/doctor.dart';
import 'src/commands/drive.dart';
import 'src/commands/emulators.dart';
import 'src/commands/format.dart';
import 'src/commands/fuchsia_reload.dart';
import 'src/commands/ide_config.dart';
@ -57,6 +58,7 @@ Future<Null> main(List<String> args) async {
new DevicesCommand(),
new DoctorCommand(verbose: verbose),
new DriveCommand(),
new EmulatorsCommand(),
new FormatCommand(),
new FuchsiaReloadCommand(),
new IdeConfigCommand(hidden: !verboseHelp),

View File

@ -0,0 +1,60 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart';
import '../android/android_sdk.dart';
import '../android/android_workflow.dart';
import '../base/process.dart';
import '../emulator.dart';
import 'android_sdk.dart';
class AndroidEmulators extends EmulatorDiscovery {
@override
bool get supportsPlatform => true;
@override
bool get canListAnything => androidWorkflow.canListDevices;
@override
Future<List<Emulator>> get emulators async => getEmulatorAvds();
}
class AndroidEmulator extends Emulator {
AndroidEmulator(
String id
) : super(id);
@override
String get name => id;
// @override
// Future<bool> launch() async {
// // TODO: ...
// return null;Í
// }
}
/// Return the list of available emulator AVDs.
List<AndroidEmulator> getEmulatorAvds() {
final String emulatorPath = getEmulatorPath(androidSdk);
if (emulatorPath == null)
return <AndroidEmulator>[];
final String text = runSync(<String>[emulatorPath, '-list-avds']);
final List<AndroidEmulator> devices = <AndroidEmulator>[];
parseEmulatorAvdOutput(text, devices);
return devices;
}
/// Parse the given `emulator -list-avds` output in [text], and fill out the given list
/// of emulators.
@visibleForTesting
void parseEmulatorAvdOutput(String text,
List<AndroidEmulator> emulators) {
for (String line in text.trim().split('\n')) {
emulators.add(new AndroidEmulator(line));
}
}

View File

@ -59,6 +59,23 @@ String getAdbPath([AndroidSdk existingSdk]) {
}
}
/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that.
/// This should be used over accessing androidSdk.adbPath directly because it
/// will work for those users who have Android Platform 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;
}
}
class AndroidSdk {
AndroidSdk(this.directory, [this.ndkDirectory, this.ndkCompiler,
this.ndkCompilerArgs]) {
@ -200,6 +217,8 @@ class AndroidSdk {
String get adbPath => getPlatformToolsPath('adb');
String get emulatorPath => getToolsPath('emulator');
/// 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() {
@ -216,6 +235,10 @@ class AndroidSdk {
return fs.path.join(directory, 'platform-tools', binaryName);
}
String getToolsPath(String binaryName) {
return fs.path.join(directory, 'tools', binaryName);
}
void _init() {
Iterable<Directory> platforms = <Directory>[]; // android-22, ...

View File

@ -42,6 +42,12 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
@override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
@override
bool get canListEmulators => getEmulatorPath(androidSdk) != null;
@override
bool get canLaunchEmulators => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
/// Returns false if we cannot determine the Java version or if the version

View File

@ -0,0 +1,51 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import '../base/common.dart';
import '../base/utils.dart';
import '../doctor.dart';
import '../emulator.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
class EmulatorsCommand extends FlutterCommand {
@override
final String name = 'emulators';
@override
final String description = 'List all available emulators.';
@override
Future<Null> runCommand() async {
if (!doctor.canListAnything) {
throwToolExit(
"Unable to locate emulators; please run 'flutter doctor' for "
'information about installing additional components.',
exitCode: 1);
}
final List<Emulator> emulators = await emulatorManager.getAllAvailableEmulators().toList();
if (emulators.isEmpty) {
printStatus(
'No emulators available.\n\n'
// TODO: Change these when we support creation
// 'You may need to create images using "flutter emulators --create"\n'
'You may need to create one using Android Studio\n'
'or visit https://flutter.io/setup/ for troubleshooting tips.');
final List<String> diagnostics = await emulatorManager.getEmulatorDiagnostics();
if (diagnostics.isNotEmpty) {
printStatus('');
for (String diagnostic in diagnostics) {
printStatus('${diagnostic.replaceAll('\n', '\n ')}');
}
}
} else {
printStatus('${emulators.length} available ${pluralize('emulators', emulators.length)}:\n');
await Emulator.printEmulators(emulators);
}
}
}

View File

@ -26,6 +26,7 @@ import 'compile.dart';
import 'devfs.dart';
import 'device.dart';
import 'doctor.dart';
import 'emulator.dart';
import 'ios/cocoapods.dart';
import 'ios/ios_workflow.dart';
import 'ios/mac.dart';
@ -58,6 +59,7 @@ Future<T> runInContext<T>(
DeviceManager: () => new DeviceManager(),
Doctor: () => const Doctor(),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
EmulatorManager: () => new EmulatorManager(),
Flags: () => const EmptyFlags(),
FlutterVersion: () => new FlutterVersion(const Clock()),
GenSnapshot: () => const GenSnapshot(),

View File

@ -209,6 +209,12 @@ abstract class Workflow {
/// Could this thing launch *something*? It may still have minor issues.
bool get canLaunchDevices;
/// Are we functional enough to list emulators?
bool get canListEmulators;
/// Could this thing launch *something*? It may still have minor issues.
bool get canLaunchEmulators;
}
enum ValidationType {

View File

@ -0,0 +1,168 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math;
import 'android/android_emulator.dart';
import 'base/context.dart';
import 'globals.dart';
EmulatorManager get emulatorManager => context[EmulatorManager];
/// A class to get all available emulators.
class EmulatorManager {
/// Constructing EmulatorManager is cheap; they only do expensive work if some
/// of their methods are called.
EmulatorManager() {
// Register the known discoverers.
_emulatorDiscoverers.add(new AndroidEmulators());
}
final List<EmulatorDiscovery> _emulatorDiscoverers = <EmulatorDiscovery>[];
String _specifiedEmulatorId;
/// A user-specified emulator ID.
String get specifiedEmulatorId {
if (_specifiedEmulatorId == null || _specifiedEmulatorId == 'all')
return null;
return _specifiedEmulatorId;
}
set specifiedEmulatorId(String id) {
_specifiedEmulatorId = id;
}
/// True when the user has specified a single specific emulator.
bool get hasSpecifiedEmulatorId => specifiedEmulatorId != null;
/// True when the user has specified all emulators by setting
/// specifiedEmulatorId = 'all'.
bool get hasSpecifiedAllEmulators => _specifiedEmulatorId == 'all';
Stream<Emulator> getEmulatorsById(String emulatorId) async* {
final List<Emulator> emulators = await getAllAvailableEmulators().toList();
emulatorId = emulatorId.toLowerCase();
bool exactlyMatchesEmulatorId(Emulator emulator) =>
emulator.id.toLowerCase() == emulatorId ||
emulator.name.toLowerCase() == emulatorId;
bool startsWithEmulatorId(Emulator emulator) =>
emulator.id.toLowerCase().startsWith(emulatorId) ||
emulator.name.toLowerCase().startsWith(emulatorId);
final Emulator exactMatch = emulators.firstWhere(
exactlyMatchesEmulatorId, orElse: () => null);
if (exactMatch != null) {
yield exactMatch;
return;
}
// Match on a id or name starting with [emulatorId].
for (Emulator emulator in emulators.where(startsWithEmulatorId))
yield emulator;
}
/// Return the list of available emulators, filtered by any user-specified emulator id.
Stream<Emulator> getEmulators() {
return hasSpecifiedEmulatorId
? getEmulatorsById(specifiedEmulatorId)
: getAllAvailableEmulators();
}
Iterable<EmulatorDiscovery> get _platformDiscoverers {
return _emulatorDiscoverers.where((EmulatorDiscovery discoverer) => discoverer.supportsPlatform);
}
/// Return the list of all connected emulators.
Stream<Emulator> getAllAvailableEmulators() async* {
for (EmulatorDiscovery discoverer in _platformDiscoverers) {
for (Emulator emulator in await discoverer.emulators) {
yield emulator;
}
}
}
/// Whether we're capable of listing any emulators given the current environment configuration.
bool get canListAnything {
return _platformDiscoverers.any((EmulatorDiscovery discoverer) => discoverer.canListAnything);
}
/// Get diagnostics about issues with any emulators.
Future<List<String>> getEmulatorDiagnostics() async {
final List<String> diagnostics = <String>[];
for (EmulatorDiscovery discoverer in _platformDiscoverers) {
diagnostics.addAll(await discoverer.getDiagnostics());
}
return diagnostics;
}
}
/// An abstract class to discover and enumerate a specific type of emulators.
abstract class EmulatorDiscovery {
bool get supportsPlatform;
/// Whether this emulator discovery is capable of listing any emulators given the
/// current environment configuration.
bool get canListAnything;
Future<List<Emulator>> get emulators;
/// Gets a list of diagnostic messages pertaining to issues with any available
/// emulators (will be an empty list if there are no issues).
Future<List<String>> getDiagnostics() => new Future<List<String>>.value(<String>[]);
}
abstract class Emulator {
Emulator(this.id);
final String id;
String get name;
@override
int get hashCode => id.hashCode;
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! Emulator)
return false;
return id == other.id;
}
@override
String toString() => name;
static Stream<String> descriptions(List<Emulator> emulators) async* {
if (emulators.isEmpty)
return;
// Extract emulators information
final List<List<String>> table = <List<String>>[];
for (Emulator emulator in emulators) {
table.add(<String>[
emulator.name,
emulator.id,
]);
}
// Calculate column widths
final List<int> indices = new List<int>.generate(table[0].length - 1, (int i) => i);
List<int> widths = indices.map((int i) => 0).toList();
for (List<String> row in table) {
widths = indices.map((int i) => math.max(widths[i], row[i].length)).toList();
}
// Join columns into lines of text
for (List<String> row in table) {
yield indices.map((int i) => row[i].padRight(widths[i])).join('') + '${row.last}';
}
}
static Future<Null> printEmulators(List<Emulator> emulators) async {
await descriptions(emulators).forEach(printStatus);
}
}

View File

@ -30,6 +30,12 @@ class IOSWorkflow extends DoctorValidator implements Workflow {
@override
bool get canLaunchDevices => xcode.isInstalledAndMeetsVersionCheck;
@override
bool get canListEmulators => false;
@override
bool get canLaunchEmulators => false;
Future<bool> get hasIDeviceInstaller => exitsHappyAsync(<String>['ideviceinstaller', '-h']);
Future<bool> get hasIosDeploy => exitsHappyAsync(<String>['ios-deploy', '--version']);