
This PR attempts to improve clarity of androids section of `flutter doctor -v` output by providing explicit information about which JDK is being used and how to configure a different one if needed. ### Before ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java ``` ### After 1. When JDK is from Android Studio: ```console • Java binary at: /Users/users/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This is the JDK bundled with latest Android Studio installation To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` 2. When JDK is from JAVA_HOME env variable: ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK is specified by JAVA_HOME environment variable To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` 3. When path to JDK is set in flutter config: ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK was found in system PATH To change current JDK, run: `flutter config --jdk-dir="path/to/jdk"` ``` 4. When java binary is found in PATH (as fallback) ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK is specified in Flutter configuration To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` ### Motivation I think it's described in https://github.com/flutter/flutter/issues/153156#issuecomment-2336814991. TLDR; many developers struggle with Java-related issues and more verbose doctor's output will (presumably) improve DX in that part. fixes #153156 ## 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 --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
267 lines
8.9 KiB
Dart
267 lines
8.9 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:process/process.dart';
|
|
|
|
import '../base/config.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/logger.dart';
|
|
import '../base/os.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/process.dart';
|
|
import '../base/version.dart';
|
|
import 'android_studio.dart';
|
|
|
|
const String _javaExecutable = 'java';
|
|
|
|
enum JavaSource {
|
|
/// JDK bundled with latest Android Studio installation.
|
|
androidStudio,
|
|
/// JDK specified by the system's JAVA_HOME environment variable.
|
|
javaHome,
|
|
/// JDK available through the system's PATH environment variable.
|
|
path,
|
|
/// JDK specified in Flutter's configuration.
|
|
flutterConfig,
|
|
}
|
|
|
|
typedef _JavaHomePathWithSource = ({String path, JavaSource source});
|
|
|
|
/// Represents an installation of Java.
|
|
class Java {
|
|
Java({
|
|
required this.javaHome,
|
|
required this.binaryPath,
|
|
required this.javaSource,
|
|
required Logger logger,
|
|
required FileSystem fileSystem,
|
|
required OperatingSystemUtils os,
|
|
required Platform platform,
|
|
required ProcessManager processManager,
|
|
}): _logger = logger,
|
|
_fileSystem = fileSystem,
|
|
_os = os,
|
|
_platform = platform,
|
|
_processManager = processManager,
|
|
_processUtils = ProcessUtils(processManager: processManager, logger: logger);
|
|
|
|
/// Within the Java ecosystem, this environment variable is typically set
|
|
/// the install location of a Java Runtime Environment (JRE) or Java
|
|
/// Development Kit (JDK).
|
|
///
|
|
/// Tools that depend on Java and need to find it will often check this
|
|
/// variable. If you are looking to set `JAVA_HOME` when stating a process,
|
|
/// consider using the [environment] instance property instead.
|
|
static String javaHomeEnvironmentVariable = 'JAVA_HOME';
|
|
|
|
/// Finds the Java runtime environment that should be used for all java-dependent
|
|
/// operations across the tool.
|
|
///
|
|
/// This searches for Java in the following places, in order:
|
|
///
|
|
/// 1. the runtime environment bundled with Android Studio;
|
|
/// 2. the runtime environment found in the JAVA_HOME env variable, if set; or
|
|
/// 3. the java binary found on PATH.
|
|
///
|
|
/// Returns null if no java binary could be found.
|
|
static Java? find({
|
|
required Config config,
|
|
required AndroidStudio? androidStudio,
|
|
required Logger logger,
|
|
required FileSystem fileSystem,
|
|
required Platform platform,
|
|
required ProcessManager processManager,
|
|
}) {
|
|
final OperatingSystemUtils os = OperatingSystemUtils(
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
platform: platform,
|
|
processManager: processManager
|
|
);
|
|
final _JavaHomePathWithSource? home = _findJavaHome(
|
|
config: config,
|
|
logger: logger,
|
|
androidStudio: androidStudio,
|
|
platform: platform
|
|
);
|
|
final String? binary = _findJavaBinary(
|
|
logger: logger,
|
|
javaHome: home?.path,
|
|
fileSystem: fileSystem,
|
|
operatingSystemUtils: os,
|
|
platform: platform
|
|
);
|
|
|
|
if (binary == null) {
|
|
return null;
|
|
}
|
|
|
|
// If javaHome == null and binary is not null, it means that
|
|
// binary obtained from PATH as fallback.
|
|
final JavaSource javaSource = home?.source ?? JavaSource.path;
|
|
|
|
return Java(
|
|
javaHome: home?.path,
|
|
binaryPath: binary,
|
|
javaSource: javaSource,
|
|
logger: logger,
|
|
fileSystem: fileSystem,
|
|
os: os,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
);
|
|
}
|
|
|
|
/// The path of the runtime environments' home directory.
|
|
///
|
|
/// This should only be used for logging and validation purposes.
|
|
/// If you need to set JAVA_HOME when starting a process, consider
|
|
/// using [environment] instead.
|
|
/// If you need to inspect the files of the runtime, considering adding
|
|
/// a new method to this class instead.
|
|
final String? javaHome;
|
|
|
|
/// The path of the runtime environments' java binary.
|
|
///
|
|
/// This should be only used for logging and validation purposes.
|
|
/// If you need to invoke the binary directly, consider adding a new method
|
|
/// to this class instead.
|
|
final String binaryPath;
|
|
|
|
/// Indicates the source from where the Java runtime was located.
|
|
///
|
|
/// This information is useful for debugging and logging purposes to track
|
|
/// which source was used to locate the Java runtime environment.
|
|
final JavaSource javaSource;
|
|
|
|
final Logger _logger;
|
|
final FileSystem _fileSystem;
|
|
final OperatingSystemUtils _os;
|
|
final Platform _platform;
|
|
final ProcessManager _processManager;
|
|
final ProcessUtils _processUtils;
|
|
|
|
/// Returns an environment variable map with
|
|
/// 1. JAVA_HOME set if this object has a known home directory, and
|
|
/// 2. The java binary folder appended onto PATH, if the binary location is known.
|
|
///
|
|
/// This map should be used as the environment when invoking any Java-dependent
|
|
/// processes, such as Gradle or Android SDK tools (avdmanager, sdkmanager, etc.)
|
|
Map<String, String> get environment => <String, String>{
|
|
if (javaHome != null) javaHomeEnvironmentVariable: javaHome!,
|
|
'PATH': _fileSystem.path.dirname(binaryPath) +
|
|
_os.pathVarSeparator +
|
|
_platform.environment['PATH']!,
|
|
};
|
|
|
|
/// Returns the version of java in the format \d(.\d)+(.\d)+
|
|
/// Returns null if version could not be determined.
|
|
late final Version? version = (() {
|
|
if (!canRun()) {
|
|
return null;
|
|
}
|
|
|
|
final RunResult result = _processUtils.runSync(
|
|
<String>[binaryPath, '--version'],
|
|
environment: environment,
|
|
);
|
|
if (result.exitCode != 0) {
|
|
_logger.printTrace('java --version failed: exitCode: ${result.exitCode}'
|
|
' stdout: ${result.stdout} stderr: ${result.stderr}');
|
|
return null;
|
|
}
|
|
final String rawVersionOutput = result.stdout;
|
|
final List<String> versionLines = rawVersionOutput.split('\n');
|
|
// Should look something like 'openjdk 19.0.2 2023-01-17'.
|
|
final String longVersionText = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
|
|
|
|
// The contents that matter come in the format '11.0.18', '1.8.0_202 or 21'.
|
|
final RegExp jdkVersionRegex = RegExp(r'(?<version>\d+(\.\d+(\.\d+(?:_\d+)?)?)?)');
|
|
final Iterable<RegExpMatch> matches =
|
|
jdkVersionRegex.allMatches(rawVersionOutput);
|
|
if (matches.isEmpty) {
|
|
// Fallback to second string format like "java 21.0.1 2023-09-19 LTS"
|
|
final RegExp secondJdkVersionRegex =
|
|
RegExp(r'java\s+(?<version>\d+(\.\d+)?(\.\d+)?)\s+\d\d\d\d-\d\d-\d\d');
|
|
final RegExpMatch? match = secondJdkVersionRegex.firstMatch(versionLines[0]);
|
|
if (match != null) {
|
|
return Version.parse(match.namedGroup('version'));
|
|
}
|
|
_logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
|
|
return null;
|
|
}
|
|
final String? version = matches.first.namedGroup('version');
|
|
if (version == null || version.split('_').isEmpty) {
|
|
_logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
|
|
return null;
|
|
}
|
|
|
|
// Trim away _d+ from versions 1.8 and below.
|
|
final String versionWithoutBuildInfo = version.split('_').first;
|
|
|
|
final Version? parsedVersion = Version.parse(versionWithoutBuildInfo);
|
|
if (parsedVersion == null) {
|
|
return null;
|
|
}
|
|
return Version.withText(
|
|
parsedVersion.major,
|
|
parsedVersion.minor,
|
|
parsedVersion.patch,
|
|
longVersionText,
|
|
);
|
|
})();
|
|
|
|
bool canRun() {
|
|
return _processManager.canRun(binaryPath);
|
|
}
|
|
}
|
|
|
|
_JavaHomePathWithSource? _findJavaHome({
|
|
required Config config,
|
|
required Logger logger,
|
|
required AndroidStudio? androidStudio,
|
|
required Platform platform,
|
|
}) {
|
|
final Object? configured = config.getValue('jdk-dir');
|
|
if (configured != null) {
|
|
return (path: configured as String, source: JavaSource.flutterConfig);
|
|
}
|
|
|
|
final String? androidStudioJavaPath = androidStudio?.javaPath;
|
|
if (androidStudioJavaPath != null) {
|
|
return (path: androidStudioJavaPath, source: JavaSource.androidStudio);
|
|
}
|
|
|
|
final String? javaHomeEnv = platform.environment[Java.javaHomeEnvironmentVariable];
|
|
if (javaHomeEnv != null) {
|
|
return (path: javaHomeEnv, source: JavaSource.javaHome);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String? _findJavaBinary({
|
|
required Logger logger,
|
|
required String? javaHome,
|
|
required FileSystem fileSystem,
|
|
required OperatingSystemUtils operatingSystemUtils,
|
|
required Platform platform,
|
|
}) {
|
|
if (javaHome != null) {
|
|
return fileSystem.path.join(javaHome, 'bin', 'java');
|
|
}
|
|
|
|
// Fallback to PATH based lookup.
|
|
return operatingSystemUtils.which(_javaExecutable)?.path;
|
|
}
|
|
|
|
// Returns a user visible String that says the tool failed to parse
|
|
// the version of java along with the output.
|
|
String _formatJavaVersionWarning(String javaVersionRaw) {
|
|
return 'Could not parse java version from: \n'
|
|
'$javaVersionRaw \n'
|
|
'If there is a version please look for an existing bug '
|
|
'https://github.com/flutter/flutter/issues/ '
|
|
'and if one does not exist file a new issue.';
|
|
}
|