Skip to content

Commit

Permalink
[tool] Move Java functions to their own file (flutter#126086)
Browse files Browse the repository at this point in the history
This is the first step in unifying Java-finding logic across the tool. If curious, see flutter#126029 for an example of what all the changes will probably entail.

Moves java-related functionality like `AndroidSdk.findJavaHome` to a new class, `Java`.

See tracking issue flutter#126126 for more.
  • Loading branch information
andrewkolos authored and Casey Hillers committed May 24, 2023
1 parent 1eb47af commit a5fd7ca
Show file tree
Hide file tree
Showing 21 changed files with 706 additions and 567 deletions.
172 changes: 7 additions & 165 deletions packages/flutter_tools/lib/src/android/android_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';

import '../base/common.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/version.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import 'android_studio.dart';
import 'java.dart';

// ANDROID_HOME is deprecated.
// See https://developer.android.com/studio/command-line/variables.html#envar
Expand All @@ -36,16 +32,18 @@ final RegExp _sdkVersionRe = RegExp(r'^ro.build.version.sdk=([0-9]+)$');
// $ANDROID_SDK_ROOT/platforms/android-23/android.jar
// $ANDROID_SDK_ROOT/platforms/android-N/android.jar
class AndroidSdk {
AndroidSdk(this.directory) {
AndroidSdk(this.directory, {
Java? java,
}): _java = java {
reinitialize();
}

static const String javaHomeEnvironmentVariable = 'JAVA_HOME';
static const String _javaExecutable = 'java';

/// The Android SDK root directory.
final Directory directory;

final Java? _java;

List<AndroidSdkVersion> _sdkVersions = <AndroidSdkVersion>[];
AndroidSdkVersion? _latestVersion;

Expand Down Expand Up @@ -411,162 +409,6 @@ class AndroidSdk {
return null;
}

/// Returns the version of java in the format \d(.\d)+(.\d)+
/// Returns null if version not found.
String? getJavaVersion({
required AndroidStudio? androidStudio,
required FileSystem fileSystem,
required OperatingSystemUtils operatingSystemUtils,
required Platform platform,
required ProcessUtils processUtils,
}) {
final String? javaBinary = findJavaBinary(
androidStudio: androidStudio,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
platform: platform,
);
if (javaBinary == null) {
globals.printTrace('Could not find java binary to get version.');
return null;
}
final RunResult result = processUtils.runSync(
<String>[javaBinary, '--version'],
environment: sdkManagerEnv,
);
if (result.exitCode != 0) {
globals.printTrace(
'java --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
return null;
}
return parseJavaVersion(result.stdout);
}

/// Extracts JDK version from the output of java --version.
@visibleForTesting
static String? parseJavaVersion(String rawVersionOutput) {
// The contents that matter come in the format '11.0.18' or '1.8.0_202'.
final RegExp jdkVersionRegex = RegExp(r'\d+\.\d+(\.\d+(?:_\d+)?)?');
final Iterable<RegExpMatch> matches =
jdkVersionRegex.allMatches(rawVersionOutput);
if (matches.isEmpty) {
globals.logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
return null;
}
final String? versionString = matches.first.group(0);
if (versionString == null || versionString.split('_').isEmpty) {
globals.logger.printWarning(_formatJavaVersionWarning(rawVersionOutput));
return null;
}
// Trim away _d+ from versions 1.8 and below.
return versionString.split('_').first;
}

/// A value that would be appropriate to use as JAVA_HOME.
///
/// This method considers jdk in the following order:
/// * the JDK bundled with Android Studio, if one is found;
/// * the JAVA_HOME in the ambient environment, if set;
String? get javaHome {
return findJavaHome(
androidStudio: globals.androidStudio,
fileSystem: globals.fs,
operatingSystemUtils: globals.os,
platform: globals.platform,
);
}


static String? findJavaHome({
required AndroidStudio? androidStudio,
required FileSystem fileSystem,
required OperatingSystemUtils operatingSystemUtils,
required Platform platform,
}) {
if (androidStudio?.javaPath != null) {
globals.printTrace("Using Android Studio's java.");
return androidStudio!.javaPath!;
}

final String? javaHomeEnv = platform.environment[javaHomeEnvironmentVariable];
if (javaHomeEnv != null) {
globals.printTrace('Using JAVA_HOME from environment valuables.');
return javaHomeEnv;
}
return null;
}

/// Finds the java binary that is used for all operations across the tool.
///
/// This comes from [findJavaHome] if that method returns non-null;
/// otherwise, it gets from searching PATH.
// TODO(andrewkolos): To prevent confusion when debugging Android-related
// issues (see https://github.com/flutter/flutter/issues/122609 for an example),
// this logic should be consistently followed by any Java-dependent operation
// across the the tool (building Android apps, interacting with the Android SDK, etc.).
// Currently, this consistency is fragile since the logic used for building
// Android apps exists independently of this method.
// See https://github.com/flutter/flutter/issues/124252.
static String? findJavaBinary({
required AndroidStudio? androidStudio,
required FileSystem fileSystem,
required OperatingSystemUtils operatingSystemUtils,
required Platform platform,
}) {
final String? javaHome = findJavaHome(
androidStudio: androidStudio,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
platform: platform,
);

if (javaHome != null) {
return fileSystem.path.join(javaHome, 'bin', 'java');
}

// Fallback to PATH based lookup.
final String? pathJava = operatingSystemUtils.which(_javaExecutable)?.path;
if (pathJava != null) {
globals.printTrace('Using java from PATH.');
} else {
globals.printTrace('Could not find java path.');
}
return pathJava;
}

// Returns a user visible String that says the tool failed to parse
// the version of java along with the output.
static 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.';
}

Map<String, String>? _sdkManagerEnv;

/// Returns an environment with the Java folder added to PATH for use in calling
/// Java-based Android SDK commands such as sdkmanager and avdmanager.
Map<String, String> get sdkManagerEnv {
if (_sdkManagerEnv == null) {
// If we can locate Java, then add it to the path used to run the Android SDK manager.
_sdkManagerEnv = <String, String>{};
final String? javaBinary = findJavaBinary(
androidStudio: globals.androidStudio,
fileSystem: globals.fs,
operatingSystemUtils: globals.os,
platform: globals.platform,
);
if (javaBinary != null && globals.platform.environment['PATH'] != null) {
_sdkManagerEnv!['PATH'] = globals.fs.path.dirname(javaBinary) +
globals.os.pathVarSeparator +
globals.platform.environment['PATH']!;
}
}
return _sdkManagerEnv!;
}

/// Returns the version of the Android SDK manager tool or null if not found.
String? get sdkManagerVersion {
if (sdkManagerPath == null || !globals.processManager.canRun(sdkManagerPath)) {
Expand All @@ -577,7 +419,7 @@ class AndroidSdk {
}
final RunResult result = globals.processUtils.runSync(
<String>[sdkManagerPath!, '--version'],
environment: sdkManagerEnv,
environment: _java?.environment,
);
if (result.exitCode != 0) {
globals.printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
Expand Down
37 changes: 17 additions & 20 deletions packages/flutter_tools/lib/src/android/android_workflow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/user_messages.dart' hide userMessages;
import '../base/version.dart';
Expand All @@ -20,6 +19,7 @@ import '../doctor_validator.dart';
import '../features.dart';
import 'android_sdk.dart';
import 'android_studio.dart';
import 'java.dart';

const int kAndroidSdkMinVersion = 29;
final Version kAndroidJavaMinVersion = Version(1, 8, 0);
Expand Down Expand Up @@ -86,12 +86,6 @@ class AndroidValidator extends DoctorValidator {
_androidStudio = androidStudio,
_fileSystem = fileSystem,
_logger = logger,
_operatingSystemUtils = OperatingSystemUtils(
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
),
_platform = platform,
_processManager = processManager,
_userMessages = userMessages,
Expand All @@ -101,7 +95,6 @@ class AndroidValidator extends DoctorValidator {
final AndroidStudio? _androidStudio;
final FileSystem _fileSystem;
final Logger _logger;
final OperatingSystemUtils _operatingSystemUtils;
final Platform _platform;
final ProcessManager _processManager;
final UserMessages _userMessages;
Expand Down Expand Up @@ -138,6 +131,8 @@ class AndroidValidator extends DoctorValidator {
}
String? javaVersionText;
try {
// TODO(andrewkolos): Use Java class to find version instead of using duplicate
// code. See https://github.com/flutter/flutter/issues/124252.
_logger.printTrace('java -version');
final ProcessResult result = await _processManager.run(<String>[javaBinary, '-version']);
if (result.exitCode == 0) {
Expand Down Expand Up @@ -240,12 +235,13 @@ class AndroidValidator extends DoctorValidator {

_task = 'Finding Java binary';
// Now check for the JDK.
final String? javaBinary = AndroidSdk.findJavaBinary(
final String? javaBinary = Java.find(
logger: _logger,
androidStudio: _androidStudio,
fileSystem: _fileSystem,
operatingSystemUtils: _operatingSystemUtils,
platform: _platform,
);
processManager: _processManager,
)?.binaryPath;
if (javaBinary == null) {
messages.add(ValidationMessage.error(_userMessages.androidMissingJdk));
return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
Expand All @@ -266,18 +262,18 @@ class AndroidValidator extends DoctorValidator {
/// SDK have been accepted.
class AndroidLicenseValidator extends DoctorValidator {
AndroidLicenseValidator({
required Java? java,
required AndroidSdk? androidSdk,
required Platform platform,
required OperatingSystemUtils operatingSystemUtils,
required FileSystem fileSystem,
required ProcessManager processManager,
required Logger logger,
required AndroidStudio? androidStudio,
required Stdio stdio,
required UserMessages userMessages,
}) : _androidSdk = androidSdk,
}) : _java = java,
_androidSdk = androidSdk,
_platform = platform,
_operatingSystemUtils = operatingSystemUtils,
_fileSystem = fileSystem,
_processManager = processManager,
_logger = logger,
Expand All @@ -286,10 +282,10 @@ class AndroidLicenseValidator extends DoctorValidator {
_userMessages = userMessages,
super('Android license subvalidator');

final Java? _java;
final AndroidSdk? _androidSdk;
final AndroidStudio? _androidStudio;
final Stdio _stdio;
final OperatingSystemUtils _operatingSystemUtils;
final Platform _platform;
final FileSystem _fileSystem;
final ProcessManager _processManager;
Expand Down Expand Up @@ -330,12 +326,13 @@ class AndroidLicenseValidator extends DoctorValidator {
}

Future<bool> _checkJavaVersionNoOutput() async {
final String? javaBinary = AndroidSdk.findJavaBinary(
final String? javaBinary = Java.find(
logger: _logger,
androidStudio: _androidStudio,
fileSystem: _fileSystem,
operatingSystemUtils: _operatingSystemUtils,
platform: _platform,
);
processManager: _processManager,
)?.binaryPath;
if (javaBinary == null) {
return false;
}
Expand Down Expand Up @@ -387,7 +384,7 @@ class AndroidLicenseValidator extends DoctorValidator {
try {
final Process process = await _processManager.start(
<String>[_androidSdk!.sdkManagerPath!, '--licenses'],
environment: _androidSdk!.sdkManagerEnv,
environment: _java?.environment,
);
process.stdin.write('n\n');
// We expect logcat streams to occasionally contain invalid utf-8,
Expand Down Expand Up @@ -427,7 +424,7 @@ class AndroidLicenseValidator extends DoctorValidator {
try {
final Process process = await _processManager.start(
<String>[_androidSdk!.sdkManagerPath!, '--licenses'],
environment: _androidSdk!.sdkManagerEnv,
environment: _java?.environment,
);

// The real stdin will never finish streaming. Pipe until the child process
Expand Down

0 comments on commit a5fd7ca

Please sign in to comment.