1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:process/process.dart';
6
7import '../base/config.dart';
8import '../base/file_system.dart';
9import '../base/logger.dart';
10import '../base/os.dart';
11import '../base/platform.dart';
12import '../base/process.dart';
13import '../base/version.dart';
14import 'android_studio.dart';
15
16const String _javaExecutable = 'java';
17
18enum JavaSource {
19 /// JDK bundled with latest Android Studio installation.
20 androidStudio,
21
22 /// JDK specified by the system's JAVA_HOME environment variable.
23 javaHome,
24
25 /// JDK available through the system's PATH environment variable.
26 path,
27
28 /// JDK specified in Flutter's configuration.
29 flutterConfig,
30}
31
32typedef _JavaHomePathWithSource = ({String path, JavaSource source});
33
34/// Represents an installation of Java.
35class Java {
36 Java({
37 required this.javaHome,
38 required this.binaryPath,
39 required this.javaSource,
40 required Logger logger,
41 required FileSystem fileSystem,
42 required OperatingSystemUtils os,
43 required Platform platform,
44 required ProcessManager processManager,
45 }) : _logger = logger,
46 _fileSystem = fileSystem,
47 _os = os,
48 _platform = platform,
49 _processManager = processManager,
50 _processUtils = ProcessUtils(processManager: processManager, logger: logger);
51
52 /// Within the Java ecosystem, this environment variable is typically set
53 /// the install location of a Java Runtime Environment (JRE) or Java
54 /// Development Kit (JDK).
55 ///
56 /// Tools that depend on Java and need to find it will often check this
57 /// variable. If you are looking to set `JAVA_HOME` when stating a process,
58 /// consider using the [environment] instance property instead.
59 static String javaHomeEnvironmentVariable = 'JAVA_HOME';
60
61 /// Finds the Java runtime environment that should be used for all java-dependent
62 /// operations across the tool.
63 ///
64 /// This searches for Java in the following places, in order:
65 ///
66 /// 1. the runtime environment bundled with Android Studio;
67 /// 2. the runtime environment found in the JAVA_HOME env variable, if set; or
68 /// 3. the java binary found on PATH.
69 ///
70 /// Returns null if no java binary could be found.
71 static Java? find({
72 required Config config,
73 required AndroidStudio? androidStudio,
74 required Logger logger,
75 required FileSystem fileSystem,
76 required Platform platform,
77 required ProcessManager processManager,
78 }) {
79 final OperatingSystemUtils os = OperatingSystemUtils(
80 fileSystem: fileSystem,
81 logger: logger,
82 platform: platform,
83 processManager: processManager,
84 );
85 final _JavaHomePathWithSource? home = _findJavaHome(
86 config: config,
87 logger: logger,
88 androidStudio: androidStudio,
89 platform: platform,
90 );
91 final String? binary = _findJavaBinary(
92 logger: logger,
93 javaHome: home?.path,
94 fileSystem: fileSystem,
95 operatingSystemUtils: os,
96 platform: platform,
97 );
98
99 if (binary == null) {
100 return null;
101 }
102
103 // If javaHome == null and binary is not null, it means that
104 // binary obtained from PATH as fallback.
105 final JavaSource javaSource = home?.source ?? JavaSource.path;
106
107 return Java(
108 javaHome: home?.path,
109 binaryPath: binary,
110 javaSource: javaSource,
111 logger: logger,
112 fileSystem: fileSystem,
113 os: os,
114 platform: platform,
115 processManager: processManager,
116 );
117 }
118
119 /// The path of the runtime environments' home directory.
120 ///
121 /// This should only be used for logging and validation purposes.
122 /// If you need to set JAVA_HOME when starting a process, consider
123 /// using [environment] instead.
124 /// If you need to inspect the files of the runtime, considering adding
125 /// a new method to this class instead.
126 final String? javaHome;
127
128 /// The path of the runtime environments' java binary.
129 ///
130 /// This should be only used for logging and validation purposes.
131 /// If you need to invoke the binary directly, consider adding a new method
132 /// to this class instead.
133 final String binaryPath;
134
135 /// Indicates the source from where the Java runtime was located.
136 ///
137 /// This information is useful for debugging and logging purposes to track
138 /// which source was used to locate the Java runtime environment.
139 final JavaSource javaSource;
140
141 final Logger _logger;
142 final FileSystem _fileSystem;
143 final OperatingSystemUtils _os;
144 final Platform _platform;
145 final ProcessManager _processManager;
146 final ProcessUtils _processUtils;
147
148 /// Returns an environment variable map with
149 /// 1. JAVA_HOME set if this object has a known home directory, and
150 /// 2. The java binary folder appended onto PATH, if the binary location is known.
151 ///
152 /// This map should be used as the environment when invoking any Java-dependent
153 /// processes, such as Gradle or Android SDK tools (avdmanager, sdkmanager, etc.)
154 Map<String, String> get environment => <String, String>{
155 if (javaHome != null) javaHomeEnvironmentVariable: javaHome!,
156 'PATH':
157 _fileSystem.path.dirname(binaryPath) +
158 _os.pathVarSeparator +
159 _platform.environment['PATH']!,
160 };
161
162 /// Returns the version of java in the format \d(.\d)+(.\d)+
163 /// Returns null if version could not be determined.
164 late final Version? version =
165 (() {
166 if (!canRun()) {
167 return null;
168 }
169
170 final RunResult result = _processUtils.runSync(<String>[
171 binaryPath,
172 '--version',
173 ], environment: environment);
174 if (result.exitCode != 0) {
175 _logger.printTrace(
176 'java --version failed: exitCode: ${result.exitCode}'
177 ' stdout: ${result.stdout} stderr: ${result.stderr}',
178 );
179 return null;
180 }
181 final String rawVersionOutput = result.stdout;
182 final List<String> versionLines = rawVersionOutput.split('\n');
183 // Should look something like 'openjdk 19.0.2 2023-01-17'.
184 final String longVersionText = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
185
186 // The contents that matter come in the format '11.0.18', '1.8.0_202 or 21'.
187 final RegExp jdkVersionRegex = RegExp(r'(?<version>\d+(\.\d+(\.\d+(?:_\d+)?)?)?)');
188 final Iterable<RegExpMatch> matches = jdkVersionRegex.allMatches(rawVersionOutput);
189 if (matches.isEmpty) {
190 // Fallback to second string format like "java 21.0.1 2023-09-19 LTS"
191 final RegExp secondJdkVersionRegex = RegExp(
192 r'java\s+(?<version>\d+(\.\d+)?(\.\d+)?)\s+\d\d\d\d-\d\d-\d\d',
193 );
194 final RegExpMatch? match = secondJdkVersionRegex.firstMatch(versionLines[0]);
195 if (match != null) {
196 return Version.parse(match.namedGroup('version'));
197 }
198 _logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
199 return null;
200 }
201 final String? version = matches.first.namedGroup('version');
202 if (version == null || version.split('_').isEmpty) {
203 _logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
204 return null;
205 }
206
207 // Trim away _d+ from versions 1.8 and below.
208 final String versionWithoutBuildInfo = version.split('_').first;
209
210 final Version? parsedVersion = Version.parse(versionWithoutBuildInfo);
211 if (parsedVersion == null) {
212 return null;
213 }
214 return Version.withText(
215 parsedVersion.major,
216 parsedVersion.minor,
217 parsedVersion.patch,
218 longVersionText,
219 );
220 })();
221
222 bool canRun() {
223 return _processManager.canRun(binaryPath);
224 }
225}
226
227_JavaHomePathWithSource? _findJavaHome({
228 required Config config,
229 required Logger logger,
230 required AndroidStudio? androidStudio,
231 required Platform platform,
232}) {
233 final Object? configured = config.getValue('jdk-dir');
234 if (configured != null) {
235 return (path: configured as String, source: JavaSource.flutterConfig);
236 }
237
238 final String? androidStudioJavaPath = androidStudio?.javaPath;
239 if (androidStudioJavaPath != null) {
240 return (path: androidStudioJavaPath, source: JavaSource.androidStudio);
241 }
242
243 final String? javaHomeEnv = platform.environment[Java.javaHomeEnvironmentVariable];
244 if (javaHomeEnv != null) {
245 return (path: javaHomeEnv, source: JavaSource.javaHome);
246 }
247 return null;
248}
249
250String? _findJavaBinary({
251 required Logger logger,
252 required String? javaHome,
253 required FileSystem fileSystem,
254 required OperatingSystemUtils operatingSystemUtils,
255 required Platform platform,
256}) {
257 if (javaHome != null) {
258 return fileSystem.path.join(javaHome, 'bin', 'java');
259 }
260
261 // Fallback to PATH based lookup.
262 return operatingSystemUtils.which(_javaExecutable)?.path;
263}
264
265// Returns a user visible String that says the tool failed to parse
266// the version of java along with the output.
267String _formatJavaVersionWarning(String javaVersionRaw) {
268 return 'Could not parse java version from: \n'
269 '$javaVersionRaw \n'
270 'If there is a version please look for an existing bug '
271 'https://github.com/flutter/flutter/issues/ '
272 'and if one does not exist file a new issue.';
273}
274

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com