Added emulator version to doctor (#167236)
fixes https://github.com/flutter/flutter/issues/146297
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 2dea1c3..9aefa8b 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -60,6 +60,43 @@
bool get canListEmulators => canListDevices && _androidSdk?.emulatorPath != null;
}
+Future<String?> getEmulatorVersion(AndroidSdk androidSdk, ProcessManager processManager) async {
+ if (!processManager.canRun(androidSdk.emulatorPath)) {
+ return null;
+ }
+
+ try {
+ final ProcessResult result = await processManager.run(<Object>[
+ androidSdk.emulatorPath!,
+ '-version',
+ ]);
+
+ if (result.exitCode != 0) {
+ return null;
+ }
+
+ final String output = result.stdout.toString().trim();
+
+ final String versionLine = output
+ .split('\n')
+ .firstWhere((String line) => line.contains('Android emulator version'), orElse: () => '');
+
+ if (versionLine.isNotEmpty) {
+ final RegExp regex = RegExp(r'Android emulator version\s+(.*)');
+ final Match? match = regex.firstMatch(versionLine);
+ if (match != null && match.groupCount >= 1) {
+ return match.group(1)?.trim();
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ } on Exception catch (e, _) {
+ return null;
+ }
+}
+
/// A validator that checks if the Android SDK and Java SDK are available and
/// installed correctly.
///
@@ -74,11 +111,13 @@
required Logger logger,
required Platform platform,
required UserMessages userMessages,
+ required ProcessManager processManager,
}) : _java = java,
_androidSdk = androidSdk,
_logger = logger,
_platform = platform,
_userMessages = userMessages,
+ _processManager = processManager,
super('Android toolchain - develop for Android devices');
final Java? _java;
@@ -86,6 +125,7 @@
final Logger _logger;
final Platform _platform;
final UserMessages _userMessages;
+ final ProcessManager _processManager;
@override
String get slowWarning => '${_task ?? 'This'} is taking a long time...';
@@ -155,6 +195,11 @@
}
messages.add(ValidationMessage(_userMessages.androidSdkLocation(androidSdk.directory.path)));
+ messages.add(
+ ValidationMessage(
+ 'Emulator version ${await getEmulatorVersion(androidSdk, _processManager) ?? 'unknown'}',
+ ),
+ );
_task = 'Validating Android SDK command line tools are available';
if (!androidSdk.cmdlineToolsAvailable) {
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 511c62c..78c74f8 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -121,6 +121,7 @@
logger: globals.logger,
platform: globals.platform,
userMessages: globals.userMessages,
+ processManager: globals.processManager,
),
AndroidWorkflow:
() => AndroidWorkflow(androidSdk: globals.androidSdk, featureFlags: featureFlags),
diff --git a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart
index 57a2718..f3702eb 100644
--- a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart
@@ -425,12 +425,19 @@
expect(stdio.writtenToStderr, contains('sdkmanager crash'));
});
- testWithoutContext('detects license-only SDK installation with cmdline-tools', () async {
+ testUsingContext('includes emulator version', () async {
sdk
..licensesAvailable = true
..platformToolsAvailable = false
..cmdlineToolsAvailable = true
- ..directory = fileSystem.directory('/foo/bar');
+ ..directory = fileSystem.directory('/foo/bar')
+ ..emulatorPath = 'path/to/emulator';
+ processManager.addCommand(
+ FakeCommand(
+ command: <String>[sdk.emulatorPath!, '-version'],
+ stdout: 'INFO | Android emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)',
+ ),
+ );
final ValidationResult validationResult =
await AndroidValidator(
java: FakeJava(),
@@ -438,6 +445,31 @@
logger: logger,
platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
userMessages: UserMessages(),
+ processManager: processManager,
+ ).validate();
+
+ expect(validationResult.type, ValidationType.partial);
+ expect(validationResult.messages.length > 2, isTrue);
+ final ValidationMessage sdkMessage = validationResult.messages[1];
+ expect(sdkMessage.type, ValidationMessageType.information);
+ expect(sdkMessage.message, 'Emulator version 35.2.10.0 (build_id 12414864) (CL:N/A)');
+ });
+
+ testUsingContext('detects license-only SDK installation with cmdline-tools', () async {
+ sdk
+ ..licensesAvailable = true
+ ..platformToolsAvailable = false
+ ..cmdlineToolsAvailable = true
+ ..directory = fileSystem.directory('/foo/bar')
+ ..emulatorPath = 'path/to/emulator';
+ final ValidationResult validationResult =
+ await AndroidValidator(
+ java: FakeJava(),
+ androidSdk: sdk,
+ logger: logger,
+ platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
+ userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(validationResult.type, ValidationType.partial);
@@ -464,7 +496,8 @@
// Test with invalid SDK and build tools
..directory = fileSystem.directory('/foo/bar')
..sdkManagerPath = '/foo/bar/sdkmanager'
- ..latestVersion = sdkVersion;
+ ..latestVersion = sdkVersion
+ ..emulatorPath = 'path/to/emulator';
final String errorMessage = UserMessages().androidSdkBuildToolsOutdated(
kAndroidSdkMinVersion,
@@ -478,6 +511,7 @@
logger: logger,
platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
userMessages: UserMessages(),
+ processManager: processManager,
);
ValidationResult validationResult = await androidValidator.validate();
@@ -505,12 +539,13 @@
);
});
- testWithoutContext('detects missing cmdline tools', () async {
+ testUsingContext('detects missing cmdline tools', () async {
sdk
..licensesAvailable = true
..platformToolsAvailable = true
..cmdlineToolsAvailable = false
- ..directory = fileSystem.directory('/foo/bar');
+ ..directory = fileSystem.directory('/foo/bar')
+ ..emulatorPath = 'path/to/emulator';
final AndroidValidator androidValidator = AndroidValidator(
java: FakeJava(),
@@ -518,6 +553,7 @@
logger: logger,
platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
userMessages: UserMessages(),
+ processManager: processManager,
);
final ValidationResult validationResult = await androidValidator.validate();
@@ -558,7 +594,8 @@
..platformToolsAvailable = true
..cmdlineToolsAvailable = true
..directory = fileSystem.directory('/foo/bar')
- ..sdkManagerPath = '/foo/bar/sdkmanager';
+ ..sdkManagerPath = '/foo/bar/sdkmanager'
+ ..emulatorPath = 'path/to/emulator';
sdk.latestVersion = sdkVersion;
const String javaVersionText = 'openjdk version "1.7.0_212"';
@@ -571,6 +608,7 @@
logger: logger,
platform: platform,
userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(validationResult.type, ValidationType.partial);
expect(validationResult.messages.last.message, errorMessage);
@@ -595,6 +633,7 @@
Java.javaHomeEnvironmentVariable: 'home/java',
},
userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(
@@ -646,46 +685,45 @@
},
);
- testWithoutContext(
- 'Mentions that JDK is provided by latest Android Studio Installation',
- () async {
- // Mock a pass through scenario to reach _checkJavaVersion()
- sdk
- ..licensesAvailable = true
- ..platformToolsAvailable = true
- ..cmdlineToolsAvailable = true
- ..directory = fileSystem.directory('/foo/bar')
- ..sdkManagerPath = '/foo/bar/sdkmanager';
+ testUsingContext('Mentions that JDK is provided by latest Android Studio Installation', () async {
+ // Mock a pass through scenario to reach _checkJavaVersion()
+ sdk
+ ..licensesAvailable = true
+ ..platformToolsAvailable = true
+ ..cmdlineToolsAvailable = true
+ ..directory = fileSystem.directory('/foo/bar')
+ ..sdkManagerPath = '/foo/bar/sdkmanager'
+ ..emulatorPath = 'path/to/emulator';
- final ValidationResult validationResult =
- await AndroidValidator(
- java: FakeJava(),
- androidSdk: sdk,
- logger: logger,
- platform: FakePlatform(),
- userMessages: UserMessages(),
- ).validate();
+ final ValidationResult validationResult =
+ await AndroidValidator(
+ java: FakeJava(),
+ androidSdk: sdk,
+ logger: logger,
+ platform: FakePlatform(),
+ userMessages: UserMessages(),
+ processManager: processManager,
+ ).validate();
- expect(
- validationResult.messages.any(
- (ValidationMessage message) => message.message.contains(
- 'This is the JDK bundled with the latest Android Studio installation on this machine.',
- ),
+ expect(
+ validationResult.messages.any(
+ (ValidationMessage message) => message.message.contains(
+ 'This is the JDK bundled with the latest Android Studio installation on this machine.',
),
- true,
- );
- expect(
- validationResult.messages.any(
- (ValidationMessage message) => message.message.contains(
- 'To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.',
- ),
+ ),
+ true,
+ );
+ expect(
+ validationResult.messages.any(
+ (ValidationMessage message) => message.message.contains(
+ 'To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.',
),
- true,
- );
- },
- );
+ ),
+ true,
+ );
+ });
- testWithoutContext(
+ testUsingContext(
"Mentions that JDK is provided by user's JAVA_HOME environment variable",
() async {
// Mock a pass through scenario to reach _checkJavaVersion()
@@ -694,7 +732,8 @@
..platformToolsAvailable = true
..cmdlineToolsAvailable = true
..directory = fileSystem.directory('/foo/bar')
- ..sdkManagerPath = '/foo/bar/sdkmanager';
+ ..sdkManagerPath = '/foo/bar/sdkmanager'
+ ..emulatorPath = 'path/to/emulator';
final ValidationResult validationResult =
await AndroidValidator(
@@ -703,6 +742,7 @@
logger: logger,
platform: FakePlatform(),
userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(
@@ -724,14 +764,15 @@
},
);
- testWithoutContext('Mentions that path to Java binary is obtained from PATH', () async {
+ testUsingContext('Mentions that path to Java binary is obtained from PATH', () async {
// Mock a pass through scenario to reach _checkJavaVersion()
sdk
..licensesAvailable = true
..platformToolsAvailable = true
..cmdlineToolsAvailable = true
..directory = fileSystem.directory('/foo/bar')
- ..sdkManagerPath = '/foo/bar/sdkmanager';
+ ..sdkManagerPath = '/foo/bar/sdkmanager'
+ ..emulatorPath = 'path/to/emulator';
final ValidationResult validationResult =
await AndroidValidator(
@@ -740,6 +781,7 @@
logger: logger,
platform: FakePlatform(),
userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(
@@ -759,14 +801,15 @@
);
});
- testWithoutContext('Mentions that JDK is provided by Flutter config', () async {
+ testUsingContext('Mentions that JDK is provided by Flutter config', () async {
// Mock a pass through scenario to reach _checkJavaVersion()
sdk
..licensesAvailable = true
..platformToolsAvailable = true
..cmdlineToolsAvailable = true
..directory = fileSystem.directory('/foo/bar')
- ..sdkManagerPath = '/foo/bar/sdkmanager';
+ ..sdkManagerPath = '/foo/bar/sdkmanager'
+ ..emulatorPath = 'path/to/emulator';
final ValidationResult validationResult =
await AndroidValidator(
@@ -775,6 +818,7 @@
logger: logger,
platform: FakePlatform(),
userMessages: UserMessages(),
+ processManager: processManager,
).validate();
expect(