blob: 47fa7d9f612d9a9173f99492984cddef7f063a12 [file] [log] [blame]
// 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';
/// Represents an installation of Java.
class Java {
Java({
required this.javaHome,
required this.binaryPath,
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 String? home = _findJavaHome(
config: config,
logger: logger,
androidStudio: androidStudio,
platform: platform
);
final String? binary = _findJavaBinary(
logger: logger,
javaHome: home,
fileSystem: fileSystem,
operatingSystemUtils: os,
platform: platform
);
if (binary == null) {
return null;
}
return Java(
javaHome: home,
binaryPath: binary,
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;
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 {
return <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);
}
}
String? _findJavaHome({
required Config config,
required Logger logger,
required AndroidStudio? androidStudio,
required Platform platform,
}) {
final Object? configured = config.getValue('jdk-dir');
if (configured != null) {
return configured as String;
}
final String? androidStudioJavaPath = androidStudio?.javaPath;
if (androidStudioJavaPath != null) {
return androidStudioJavaPath;
}
final String? javaHomeEnv = platform.environment[Java.javaHomeEnvironmentVariable];
if (javaHomeEnv != null) {
return javaHomeEnv;
}
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.';
}