Commit c5b3b3ac authored by Efthymis Sarmpanis's avatar Efthymis Sarmpanis Committed by Jonah Williams

Flutter doctor should require java 1.8+ (#41989)

parent 7cf362fc
......@@ -19,6 +19,7 @@ import '../globals.dart';
import 'android_sdk.dart';
const int kAndroidSdkMinVersion = 28;
final Version kAndroidJavaMinVersion = Version(1, 8, 0);
final Version kAndroidSdkBuildToolsMinVersion = Version(28, 0, 3);
AndroidWorkflow get androidWorkflow => context.get<AndroidWorkflow>();
......@@ -57,8 +58,19 @@ class AndroidValidator extends DoctorValidator {
String get slowWarning => '${_task ?? 'This'} is taking a long time...';
String _task;
/// Finds the semantic version anywhere in a text.
static final RegExp _javaVersionPattern = RegExp(r'(\d+)(\.(\d+)(\.(\d+))?)?');
/// `java -version` response is not only a number, but also includes other
/// information eg. `openjdk version "1.7.0_212"`.
/// This method extracts only the semantic version from from that response.
static String _extractJavaVersion(String text) {
final Match match = _javaVersionPattern.firstMatch(text ?? '');
return text?.substring(match.start, match.end);
}
/// Returns false if we cannot determine the Java version or if the version
/// is not compatible.
/// is older that the minimum allowed version of 1.8.
Future<bool> _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) async {
_task = 'Checking Java status';
try {
......@@ -66,24 +78,28 @@ class AndroidValidator extends DoctorValidator {
messages.add(ValidationMessage.error(userMessages.androidCantRunJavaBinary(javaBinary)));
return false;
}
String javaVersion;
String javaVersionText;
try {
printTrace('java -version');
final ProcessResult result = await processManager.run(<String>[javaBinary, '-version']);
if (result.exitCode == 0) {
final List<String> versionLines = result.stderr.split('\n');
javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
javaVersionText = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
}
} catch (error) {
printTrace(error.toString());
}
if (javaVersion == null) {
if (javaVersionText == null || javaVersionText.isEmpty) {
// Could not determine the java version.
messages.add(ValidationMessage.error(userMessages.androidUnknownJavaVersion));
return false;
}
messages.add(ValidationMessage(userMessages.androidJavaVersion(javaVersion)));
// TODO(johnmccutchan): Validate version.
final Version javaVersion = Version.parse(_extractJavaVersion(javaVersionText));
if (javaVersion < kAndroidJavaMinVersion) {
messages.add(ValidationMessage.error(userMessages.androidJavaMinimumVersion(javaVersionText)));
return false;
}
messages.add(ValidationMessage(userMessages.androidJavaVersion(javaVersionText)));
return true;
} finally {
_task = null;
......
......@@ -49,6 +49,7 @@ class UserMessages {
String androidCantRunJavaBinary(String javaBinary) => 'Cannot execute $javaBinary to determine the version';
String get androidUnknownJavaVersion => 'Could not determine java version';
String androidJavaVersion(String javaVersion) => 'Java version $javaVersion';
String androidJavaMinimumVersion(String javaVersion) => 'Java version $javaVersion is older than the minimum recommended version of 1.8';
String androidSdkLicenseOnly(String envKey) =>
'Android SDK contains licenses only.\n'
'Your first build of an Android application will take longer than usual, '
......
......@@ -48,13 +48,13 @@ void main() {
final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted;
expect(licenseStatus, LicensesAccepted.unknown);
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('licensesAccepted returns LicensesAccepted.unknown if cannot run sdkmanager', () async {
processManager.runSucceeds = false;
......@@ -62,13 +62,13 @@ void main() {
final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted;
expect(licenseStatus, LicensesAccepted.unknown);
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('licensesAccepted handles garbage/no output', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
......@@ -77,13 +77,13 @@ void main() {
expect(result, equals(LicensesAccepted.unknown));
expect(processManager.commands.first, equals('/foo/bar/sdkmanager'));
expect(processManager.commands.last, equals('--licenses'));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('licensesAccepted works for all licenses accepted', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
......@@ -95,13 +95,13 @@ void main() {
final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
final LicensesAccepted result = await licenseValidator.licensesAccepted;
expect(result, equals(LicensesAccepted.all));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('licensesAccepted works for some licenses accepted', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
......@@ -114,13 +114,13 @@ void main() {
final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
final LicensesAccepted result = await licenseValidator.licensesAccepted;
expect(result, equals(LicensesAccepted.some));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('licensesAccepted works for no licenses accepted', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
......@@ -133,78 +133,78 @@ void main() {
final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator();
final LicensesAccepted result = await licenseValidator.licensesAccepted;
expect(result, equals(LicensesAccepted.none));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('runLicenseManager succeeds for version >= 26', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.sdkManagerVersion).thenReturn('26.0.0');
expect(await AndroidLicenseValidator.runLicenseManager(), isTrue);
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('runLicenseManager errors for version < 26', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.sdkManagerVersion).thenReturn('25.0.0');
expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit(message: 'To update, run'));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('runLicenseManager errors correctly for null version', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.sdkManagerVersion).thenReturn(null);
expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit(message: 'To update, run'));
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('runLicenseManager errors when sdkmanager is not found', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
processManager.canRunSucceeds = false;
expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit());
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('runLicenseManager errors when sdkmanager fails to run', () async {
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
processManager.runSucceeds = false;
expect(AndroidLicenseValidator.runLicenseManager(), throwsToolExit());
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('detects license-only SDK installation', () async {
when(sdk.licensesAvailable).thenReturn(true);
......@@ -215,13 +215,13 @@ void main() {
validationResult.messages.last.message,
userMessages.androidSdkLicenseOnly(kAndroidHome),
);
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('detects minium required SDK and buildtools', () async {
final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion();
......@@ -269,12 +269,44 @@ void main() {
validationResult.messages.any((ValidationMessage message) => message.message == errorMessage),
isFalse,
);
}, overrides: <Type, Generator>{
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
ProcessManager: () => processManager,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me'},
Stdio: () => stdio,
});
}));
testUsingContext('detects minimum required java version', () async {
final AndroidSdkVersion mockSdkVersion = MockAndroidSdkVersion();
// Mock a pass through scenario to reach _checkJavaVersion()
when(sdk.licensesAvailable).thenReturn(true);
when(sdk.platformToolsAvailable).thenReturn(true);
when(mockSdkVersion.sdkLevel).thenReturn(28);
when(mockSdkVersion.buildToolsVersion).thenReturn(Version(28, 0, 3));
when(sdk.sdkManagerPath).thenReturn('/foo/bar/sdkmanager');
when(sdk.latestVersion).thenReturn(mockSdkVersion);
when(sdk.validateSdkWellFormed()).thenReturn(<String>[]);
//Test with older version of JDK
const String javaVersionText = 'openjdk version "1.7.0_212"';
when(processManager.run(argThat(contains('-version')))).thenAnswer((_) =>
Future<ProcessResult>.value(ProcessResult(0, 0, null, javaVersionText)));
final String errorMessage = userMessages.androidJavaMinimumVersion(javaVersionText);
final ValidationResult validationResult = await AndroidValidator().validate();
expect(validationResult.type, ValidationType.partial);
expect(
validationResult.messages.last.message,
errorMessage,
);
}, overrides: Map<Type, Generator>.unmodifiable(<Type, Generator>{
AndroidSdk: () => sdk,
FileSystem: () => fs,
Platform: () => FakePlatform()..environment = <String, String>{'HOME': '/home/me', 'JAVA_HOME': 'home/java'},
ProcessManager: () => processManager,
Stdio: () => stdio,
}));
}
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