diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index 8bc53d099b7c5..786cd5d914108 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -389,12 +389,16 @@ class AndroidLicenseValidator extends DoctorValidator { ), ); + final List stderrLines = []; // Wait for stdout and stderr to be fully processed, because process.exitCode // may complete first. try { await Future.wait(>[ _stdio.addStdoutStream(process.stdout), - _stdio.addStderrStream(process.stderr), + process.stderr.forEach((List event) { + _stdio.stderr.add(event); + stderrLines.add(utf8.decode(event)); + }), ]); } on Exception catch (err, stack) { _logger.printTrace('Echoing stdout or stderr from the license subprocess failed:'); @@ -403,11 +407,7 @@ class AndroidLicenseValidator extends DoctorValidator { final int exitCode = await process.exitCode; if (exitCode != 0) { - throwToolExit(_userMessages.androidCannotRunSdkManager( - _androidSdk.sdkManagerPath ?? '', - 'exited code $exitCode', - _platform, - )); + throwToolExit(_messageForSdkManagerError(stderrLines, exitCode)); } return true; } on ProcessException catch (e) { @@ -426,4 +426,30 @@ class AndroidLicenseValidator extends DoctorValidator { } return _processManager.canRun(sdkManagerPath); } + + String _messageForSdkManagerError( + List androidSdkStderr, + int exitCode, + ) { + final String sdkManagerPath = _androidSdk!.sdkManagerPath!; + + final bool failedDueToJdkIncompatibility = androidSdkStderr.join().contains( + RegExp(r'java\.lang\.UnsupportedClassVersionError.*SdkManagerCli ' + r'has been compiled by a more recent version of the Java Runtime')); + + if (failedDueToJdkIncompatibility) { + return 'Android sdkmanager tool was found, but failed to run ($sdkManagerPath): "exited code $exitCode".\n' + 'It appears the version of the Java binary used (${_java!.binaryPath}) is ' + 'too out-of-date and is incompatible with the Android sdkmanager tool.\n' + 'If the Java binary came bundled with Android Studio, consider updating ' + 'your installation of Android studio. Alternatively, you can uninstall ' + 'the Android SDK command-line tools and install an earlier version. '; + } + + return _userMessages.androidCannotRunSdkManager( + sdkManagerPath, + 'exited code $exitCode', + _platform, + ); + } } diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index 92e6feb285a61..0028f1f0ad20f 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -102,7 +102,7 @@ class UserMessages { 'Unable to locate Android SDK.\n' 'Install Android Studio from: https://developer.android.com/studio/index.html\n' 'On first launch it will assist you in installing the Android SDK components.\n' - '(or visit ${_androidSdkInstallUrl(platform)} for detailed instructions).\n' + '(or visit ${androidSdkInstallUrl(platform)} for detailed instructions).\n' 'If the Android SDK has been installed to a custom location, please use\n' '`flutter config --android-sdk` to update to that location.\n'; String androidSdkLocation(String directory) => 'Android SDK at $directory'; @@ -110,7 +110,7 @@ class UserMessages { 'Platform $platform, build-tools $tools'; String androidSdkInstallHelp(Platform platform) => 'Try re-installing or updating your Android SDK,\n' - 'visit ${_androidSdkInstallUrl(platform)} for detailed instructions.'; + 'visit ${androidSdkInstallUrl(platform)} for detailed instructions.'; // Also occurs in AndroidLicenseValidator String androidStatusInfo(String version) => 'Android SDK version $version'; @@ -126,7 +126,7 @@ class UserMessages { String androidLicensesUnknown(Platform platform) => 'Android license status unknown.\n' 'Run `flutter doctor --android-licenses` to accept the SDK licenses.\n' - 'See ${_androidSdkInstallUrl(platform)} for more details.'; + 'See ${androidSdkInstallUrl(platform)} for more details.'; String androidSdkManagerOutdated(String managerPath) => 'A newer version of the Android SDK is required. To update, run:\n' '$managerPath --update\n'; @@ -135,14 +135,14 @@ class UserMessages { String androidMissingSdkManager(String sdkManagerPath, Platform platform) => 'Android sdkmanager tool not found ($sdkManagerPath).\n' 'Try re-installing or updating your Android SDK,\n' - 'visit ${_androidSdkInstallUrl(platform)} for detailed instructions.'; + 'visit ${androidSdkInstallUrl(platform)} for detailed instructions.'; String androidCannotRunSdkManager(String sdkManagerPath, String error, Platform platform) => 'Android sdkmanager tool was found, but failed to run ($sdkManagerPath): "$error".\n' 'Try re-installing or updating your Android SDK,\n' - 'visit ${_androidSdkInstallUrl(platform)} for detailed instructions.'; + 'visit ${androidSdkInstallUrl(platform)} for detailed instructions.'; String androidSdkBuildToolsOutdated(int sdkMinVersion, String buildToolsMinVersion, Platform platform) => 'Flutter requires Android SDK $sdkMinVersion and the Android BuildTools $buildToolsMinVersion\n' - 'To update the Android SDK visit ${_androidSdkInstallUrl(platform)} for detailed instructions.'; + 'To update the Android SDK visit ${androidSdkInstallUrl(platform)} for detailed instructions.'; String get androidMissingCmdTools => 'cmdline-tools component is missing\n' 'Run `path/to/sdkmanager --install "cmdline-tools;latest"`\n' 'See https://developer.android.com/studio/command-line for more details.'; @@ -163,7 +163,7 @@ class UserMessages { 'but Android Studio not found at this location.'; String androidStudioInstallation(Platform platform) => 'Android Studio not found; download from https://developer.android.com/studio/index.html\n' - '(or visit ${_androidSdkInstallUrl(platform)} for detailed instructions).'; + '(or visit ${androidSdkInstallUrl(platform)} for detailed instructions).'; // Messages used in XcodeValidator String xcodeLocation(String location) => 'Xcode at $location'; @@ -351,7 +351,7 @@ class UserMessages { 'Read more about iOS versioning at\n' 'https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html\n'; - String _androidSdkInstallUrl(Platform platform) { + String androidSdkInstallUrl(Platform platform) { const String baseUrl = 'https://flutter.dev/docs/get-started/install'; const String fragment = '#android-setup'; if (platform.isMacOS) { 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 d138215e77416..eb6ec04e8067f 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 @@ -578,6 +578,42 @@ Review licenses that have not been accepted (y/N)? true, ); }); + + testWithoutContext('Asks user to upgrade Android Studio when it is too far behind the Android SDK', () async { + const String sdkManagerPath = '/foo/bar/sdkmanager'; + sdk.sdkManagerPath = sdkManagerPath; + final BufferLogger logger = BufferLogger.test(); + processManager.addCommand( + const FakeCommand( + command: [sdkManagerPath, '--licenses'], + exitCode: 1, + stderr: ''' +Error: LinkageError occurred while loading main class com.android.sdklib.tool.sdkmanager.SdkManagerCli + java.lang.UnsupportedClassVersionError: com/android/sdklib/tool/sdkmanager/SdkManagerCli has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0 +Android sdkmanager tool was found, but failed to run +''', + ), + ); + + final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), + androidSdk: sdk, + processManager: processManager, + platform: FakePlatform(environment: {'HOME': '/home/me'}), + stdio: stdio, + logger: logger, + userMessages: UserMessages(), + ); + + await expectLater( + licenseValidator.runLicenseManager(), + throwsToolExit( + message: RegExp('.*consider updating your installation of Android studio. Alternatively, you.*'), + ), + ); + expect(processManager, hasNoRemainingExpectations); + expect(stdio.stderr.getAndClear(), contains('UnsupportedClassVersionError')); + }); } class FakeAndroidSdk extends Fake implements AndroidSdk {