Commit 53840fb0 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by Danny Tuppeny

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
parent 21c2e47f
......@@ -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),
......
// 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));
}
}
......@@ -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, ...
......
......@@ -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
......
// 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);
}
}
}
......@@ -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(),
......
......@@ -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 {
......
// 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);
}
}
......@@ -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']);
......
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