diff --git a/packages/shorebird_cli/lib/src/commands/doctor_command.dart b/packages/shorebird_cli/lib/src/commands/doctor_command.dart index 288bd02c..0205e582 100644 --- a/packages/shorebird_cli/lib/src/commands/doctor_command.dart +++ b/packages/shorebird_cli/lib/src/commands/doctor_command.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/android_sdk.dart'; import 'package:shorebird_cli/src/android_studio.dart'; @@ -45,6 +47,7 @@ class DoctorCommand extends ShorebirdCommand { final flutterVersion = await _tryGetFlutterVersion(); final output = StringBuffer(); final shorebirdFlutterPrefix = StringBuffer('Flutter'); + if (flutterVersion != null) { shorebirdFlutterPrefix.write(' $flutterVersion'); } @@ -57,6 +60,19 @@ Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''', if (verbose) { final notDetected = red.wrap('not detected'); + var javaVersion = notDetected; + + final javaExe = java.executable; + if (javaExe != null) { + final result = java.version; + if (result != null) { + javaVersion = result + .split(Platform.lineTerminator) + // Adds empty space to the version will be padded with the + // JAVA_VERSION label. + .join('${Platform.lineTerminator} '); + } + } output.writeln(''' Logs: ${shorebirdEnv.logsDirectory.path} @@ -64,7 +80,9 @@ Android Toolchain • Android Studio: ${androidStudio.path ?? notDetected} • Android SDK: ${androidSdk.path ?? notDetected} • ADB: ${androidSdk.adbPath ?? notDetected} - • JAVA_HOME: ${java.home ?? notDetected}'''); + • JAVA_HOME: ${java.home ?? notDetected} + • JAVA_EXECUTABLE: ${javaExe ?? notDetected} + • JAVA_VERSION: $javaVersion'''); } logger.info(output.toString()); diff --git a/packages/shorebird_cli/lib/src/executables/java.dart b/packages/shorebird_cli/lib/src/executables/java.dart index 64573959..dd00d8c5 100644 --- a/packages/shorebird_cli/lib/src/executables/java.dart +++ b/packages/shorebird_cli/lib/src/executables/java.dart @@ -1,12 +1,14 @@ import 'dart:io' hide Platform; import 'package:collection/collection.dart'; +import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/android_studio.dart'; import 'package:shorebird_cli/src/extensions/string.dart'; import 'package:shorebird_cli/src/os/os.dart'; import 'package:shorebird_cli/src/platform.dart'; +import 'package:shorebird_cli/src/shorebird_process.dart'; /// A reference to a [Java] instance. final javaRef = create(Java.new); @@ -50,6 +52,19 @@ class Java { return null; } + /// Returns the version of the user's Java installation, if one is found. + String? get version { + final javaExe = executable; + if (javaExe == null) return null; + final javaVersionProcessResult = process.runSync(javaExe, ['-version']); + if (javaVersionProcessResult.exitCode == ExitCode.success.code) { + // The version string is printed to stderr for some reason. + return javaVersionProcessResult.stderr.toString(); + } + + return null; + } + /// Returns the path to the java executable relative to the Java home dir. String get _javaExecutablePath => p.join('bin', platform.isWindows ? 'java.exe' : 'java'); diff --git a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart index 51c6baf7..8ab8c988 100644 --- a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart @@ -123,10 +123,13 @@ Engine • revision $shorebirdEngineRevision await runWithOverrides(command.run); final notDetectedText = red.wrap('not detected'); - verify( - () => logger.info(''' + final msg = + verify(() => logger.info(captureAny())).captured.first as String; -Shorebird v$packageVersion • git@github.com:shorebirdtech/shorebird.git + expect( + msg, + equals(''' +Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git Flutter • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision @@ -135,8 +138,11 @@ Android Toolchain • Android Studio: $notDetectedText • Android SDK: $notDetectedText • ADB: $notDetectedText - • JAVA_HOME: $notDetectedText'''), - ).called(1); + • JAVA_HOME: $notDetectedText + • JAVA_EXECUTABLE: $notDetectedText + • JAVA_VERSION: $notDetectedText +'''), + ); }); test('prints additional information (detected)', () async { @@ -145,21 +151,44 @@ Android Toolchain when(() => androidSdk.path).thenReturn('test-sdk-path'); when(() => androidSdk.adbPath).thenReturn('test-adb-path'); when(() => java.home).thenReturn('test-java-home'); + when(() => java.executable).thenReturn('test-java-executable'); + + when(() => java.version).thenReturn( + ''' +openjdk version "17.0.9" 2023-10-17 +OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) +OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode)''' + .replaceAll('\n', Platform.lineTerminator), + ); await runWithOverrides(command.run); - verify( - () => logger.info(''' + final msg = + verify(() => logger.info(captureAny())).captured.first as String; -Shorebird v$packageVersion • git@github.com:shorebirdtech/shorebird.git + expect( + msg.replaceAll( + Platform.lineTerminator, + '\n', + ), + equals( + ''' +Shorebird $packageVersion • git@github.com:shorebirdtech/shorebird.git Flutter • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision +Logs: ${logsDirectory.path} Android Toolchain • Android Studio: test-studio-path • Android SDK: test-sdk-path • ADB: test-adb-path - • JAVA_HOME: test-java-home'''), - ).called(1); + • JAVA_HOME: test-java-home + • JAVA_EXECUTABLE: test-java-executable + • JAVA_VERSION: openjdk version "17.0.9" 2023-10-17 + OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874) + OpenJDK 64-Bit Server VM (build 17.0.9+0-17.0.9b1087.7-11185874, mixed mode) +''', + ), + ); }); }); diff --git a/packages/shorebird_cli/test/src/executables/java_test.dart b/packages/shorebird_cli/test/src/executables/java_test.dart index 29ed3a85..31113e5f 100644 --- a/packages/shorebird_cli/test/src/executables/java_test.dart +++ b/packages/shorebird_cli/test/src/executables/java_test.dart @@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/android_studio.dart'; import 'package:shorebird_cli/src/executables/executables.dart'; import 'package:shorebird_cli/src/os/os.dart'; import 'package:shorebird_cli/src/platform.dart'; +import 'package:shorebird_cli/src/shorebird_process.dart'; import 'package:test/test.dart'; import '../mocks.dart'; @@ -17,6 +18,7 @@ void main() { late AndroidStudio androidStudio; late OperatingSystemInterface osInterface; late Platform platform; + late ShorebirdProcess shorebirdProcess; late Java java; R runWithOverrides(R Function() body) { @@ -25,6 +27,7 @@ void main() { values: { androidStudioRef.overrideWith(() => androidStudio), osInterfaceRef.overrideWith(() => osInterface), + processRef.overrideWith(() => shorebirdProcess), platformRef.overrideWith(() => platform), }, ); @@ -40,6 +43,7 @@ void main() { androidStudio = MockAndroidStudio(); osInterface = MockOperatingSystemInterface(); platform = MockPlatform(); + shorebirdProcess = MockShorebirdProcess(); java = Java(); when(() => platform.environment).thenReturn({}); @@ -50,6 +54,52 @@ void main() { when(() => osInterface.which(any())).thenReturn(null); }); + group('version', () { + setUp(() { + const javaHome = '/path/to/jdk'; + when(() => platform.isWindows).thenReturn(false); + when(() => platform.environment).thenReturn({'JAVA_HOME': javaHome}); + + final processResult = MockShorebirdProcessResult(); + when(() => shorebirdProcess.runSync(any(), any())).thenReturn( + processResult, + ); + when(() => processResult.exitCode).thenReturn(0); + when(() => processResult.stderr).thenReturn('java version "11.0.1"'); + }); + + test('calls java -version and return the stderr', () { + expect( + runWithOverrides(() => java.version), + equals('java version "11.0.1"'), + ); + }); + + group('when the command fails', () { + setUp(() { + final processResult = MockShorebirdProcessResult(); + when(() => shorebirdProcess.runSync(any(), any())).thenReturn( + processResult, + ); + when(() => processResult.exitCode).thenReturn(1); + }); + + test('returns null', () { + expect(runWithOverrides(() => java.version), isNull); + }); + }); + + group('when no jdk is found', () { + setUp(() { + when(() => platform.environment).thenReturn({}); + }); + + test('returns null', () { + expect(runWithOverrides(() => java.version), isNull); + }); + }); + }); + group('executable', () { group( 'when on Windows',