Improvements to flutter doctor JDK search. (#8745)
- [x] Add custom logic on MacOS to determine if Java is installed before invoking `java`.
- [x] Check JAVA_HOME, platform specific logic, and finally PATH to locate the `java` executable.
- [x] Improved doctor messages.
Fixes #8508
Fixes #8521
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index e9977a3..ba332f4 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -5,7 +5,10 @@
import 'dart:async';
import '../base/io.dart';
+import '../base/file_system.dart';
+import '../base/os.dart';
import '../base/platform.dart';
+import '../base/process.dart';
import '../base/process_manager.dart';
import '../doctor.dart';
import '../globals.dart';
@@ -23,13 +26,71 @@
@override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
+ static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME';
+ static const String _kJavaExecutable = 'java';
+ static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
+
+ /// First sniff JAVA_HOME, then fallback to PATH.
+ String _findJavaBinary() {
+
+ final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
+ if (javaHomeEnv != null) {
+ // Trust JAVA_HOME.
+ return fs.path.join(javaHomeEnv, 'bin', 'java');
+ }
+
+ // MacOS specific logic to avoid popping up a dialog window.
+ // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
+ if (platform.isMacOS) {
+ try {
+ final String javaHomeOutput = runCheckedSync(<String>['/usr/libexec/java_home'], hideStdout: true);
+ if (javaHomeOutput != null) {
+ final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
+ if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.length > 0)) {
+ final String javaHome = javaHomeOutputSplit[0].trim();
+ return fs.path.join(javaHome, 'bin', 'java');
+ }
+ }
+ } catch (_) { /* ignore */ }
+ }
+
+ // Fallback to PATH based lookup.
+ return os.which(_kJavaExecutable)?.path;
+ }
+
+ /// Returns false if we cannot determine the Java version or if the version
+ /// is not compatible.
+ bool _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) {
+ if (!processManager.canRun(javaBinary)) {
+ messages.add(new ValidationMessage.error('Cannot execute $javaBinary to determine the version'));
+ return false;
+ }
+ String javaVersion;
+ try {
+ printTrace('java -version');
+ final ProcessResult result = processManager.runSync(<String>[javaBinary, '-version']);
+ if (result.exitCode == 0) {
+ final List<String> versionLines = result.stderr.split('\n');
+ javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
+ }
+ } catch (_) { /* ignore */ }
+ if (javaVersion == null) {
+ // Could not determine the java version.
+ messages.add(new ValidationMessage.error('Could not determine java version'));
+ return false;
+ }
+ messages.add(new ValidationMessage('Java version: $javaVersion'));
+ // TODO(johnmccutchan): Validate version.
+ return true;
+ }
+
+
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
- ValidationType type = ValidationType.missing;
- String sdkVersionText;
if (androidSdk == null) {
+ // No Android SDK found.
if (platform.environment.containsKey(kAndroidHome)) {
final String androidHomeDir = platform.environment[kAndroidHome];
messages.add(new ValidationMessage.error(
@@ -42,63 +103,57 @@
'(or visit https://flutter.io/setup/#android-setup for detailed instructions).'
));
}
- } else {
- type = ValidationType.partial;
- messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
-
- if (androidSdk.latestVersion != null) {
- sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
-
- messages.add(new ValidationMessage(
- 'Platform ${androidSdk.latestVersion.platformVersionName}, '
- 'build-tools ${androidSdk.latestVersion.buildToolsVersionName}'
- ));
- }
-
- if (platform.environment.containsKey(kAndroidHome)) {
- final String androidHomeDir = platform.environment[kAndroidHome];
- messages.add(new ValidationMessage('$kAndroidHome = $androidHomeDir'));
- }
-
- final List<String> validationResult = androidSdk.validateSdkWellFormed();
-
- if (validationResult.isEmpty) {
- // Empty result means SDK is well formed.
- // The SDK also requires a valid Java JDK installation.
- const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
- String javaVersion;
-
- try {
- printTrace('java -version');
-
- final ProcessResult result = processManager.runSync(<String>['java', '-version']);
- if (result.exitCode == 0) {
- javaVersion = result.stderr;
- final List<String> versionLines = javaVersion.split('\n');
- javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
- }
- } catch (error) {
- }
-
- if (javaVersion == null) {
- messages.add(new ValidationMessage.error(
- 'No Java Development Kit (JDK) found; you can download the JDK from $_kJdkDownload.'
- ));
- } else {
- messages.add(new ValidationMessage(javaVersion));
- type = ValidationType.installed;
- }
- } else {
- messages.addAll(validationResult.map((String message) {
- return new ValidationMessage.error(message);
- }));
- messages.add(new ValidationMessage(
- 'Try re-installing or updating your Android SDK,\n'
- 'visit https://flutter.io/setup/#android-setup for detailed instructions.'));
- }
+ return new ValidationResult(ValidationType.missing, messages);
}
- return new ValidationResult(type, messages, statusInfo: sdkVersionText);
+ messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
+
+ String sdkVersionText;
+ if (androidSdk.latestVersion != null) {
+ sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
+
+ messages.add(new ValidationMessage(
+ 'Platform ${androidSdk.latestVersion.platformVersionName}, '
+ 'build-tools ${androidSdk.latestVersion.buildToolsVersionName}'
+ ));
+ }
+
+ if (platform.environment.containsKey(kAndroidHome)) {
+ final String androidHomeDir = platform.environment[kAndroidHome];
+ messages.add(new ValidationMessage('$kAndroidHome = $androidHomeDir'));
+ }
+
+ final List<String> validationResult = androidSdk.validateSdkWellFormed();
+
+ if (validationResult.isNotEmpty) {
+ // Android SDK is not functional.
+ messages.addAll(validationResult.map((String message) {
+ return new ValidationMessage.error(message);
+ }));
+ messages.add(new ValidationMessage(
+ 'Try re-installing or updating your Android SDK,\n'
+ 'visit https://flutter.io/setup/#android-setup for detailed instructions.'));
+ return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
+ }
+
+ // Now check for the JDK.
+ final String javaBinary = _findJavaBinary();
+ if (javaBinary == null) {
+ messages.add(new ValidationMessage.error(
+ 'No Java Development Kit (JDK) found; You must have the environment '
+ 'variable JAVA_HOME set and the java binary in your PATH. '
+ 'You can download the JDK from $_kJdkDownload.'));
+ return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
+ }
+ messages.add(new ValidationMessage('Java binary at: $javaBinary'));
+
+ // Check JDK version.
+ if (!_checkJavaVersion(javaBinary, messages)) {
+ return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
+ }
+
+ // Success.
+ return new ValidationResult(ValidationType.installed, messages, statusInfo: sdkVersionText);
}
}