mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
21c2e47f29
commit
53840fb0ce
@ -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),
|
||||
|
||||
60
packages/flutter_tools/lib/src/android/android_emulator.dart
Normal file
60
packages/flutter_tools/lib/src/android/android_emulator.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
51
packages/flutter_tools/lib/src/commands/emulators.dart
Normal file
51
packages/flutter_tools/lib/src/commands/emulators.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
168
packages/flutter_tools/lib/src/emulator.dart
Normal file
168
packages/flutter_tools/lib/src/emulator.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user