diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart index ace74def97..f684844c64 100644 --- a/packages/flutter_tools/lib/src/android/android_sdk.dart +++ b/packages/flutter_tools/lib/src/android/android_sdk.dart @@ -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 @@ -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 _sdkVersions = []; AndroidSdkVersion? _latestVersion; @@ -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( - [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 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? _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 get sdkManagerEnv { - if (_sdkManagerEnv == null) { - // If we can locate Java, then add it to the path used to run the Android SDK manager. - _sdkManagerEnv = {}; - 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)) { @@ -577,7 +419,7 @@ class AndroidSdk { } final RunResult result = globals.processUtils.runSync( [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}'); diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index 99021585fc..daee70ab31 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -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'; @@ -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); @@ -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, @@ -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; @@ -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([javaBinary, '-version']); if (result.exitCode == 0) { @@ -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); @@ -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, @@ -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; @@ -330,12 +326,13 @@ class AndroidLicenseValidator extends DoctorValidator { } Future _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; } @@ -387,7 +384,7 @@ class AndroidLicenseValidator extends DoctorValidator { try { final Process process = await _processManager.start( [_androidSdk!.sdkManagerPath!, '--licenses'], - environment: _androidSdk!.sdkManagerEnv, + environment: _java?.environment, ); process.stdin.write('n\n'); // We expect logcat streams to occasionally contain invalid utf-8, @@ -427,7 +424,7 @@ class AndroidLicenseValidator extends DoctorValidator { try { final Process process = await _processManager.start( [_androidSdk!.sdkManagerPath!, '--licenses'], - environment: _androidSdk!.sdkManagerEnv, + environment: _java?.environment, ); // The real stdin will never finish streaming. Pipe until the child process diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 0ae53710cc..bc1f9c9957 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -147,8 +147,7 @@ class AndroidGradleBuilder implements AndroidBuilder { _gradleUtils = gradleUtils, _androidStudio = androidStudio, _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform), - _processUtils = ProcessUtils(logger: logger, processManager: processManager), - _platform = platform; + _processUtils = ProcessUtils(logger: logger, processManager: processManager); final Logger _logger; final ProcessUtils _processUtils; @@ -158,7 +157,6 @@ class AndroidGradleBuilder implements AndroidBuilder { final GradleUtils _gradleUtils; final FileSystemUtils _fileSystemUtils; final AndroidStudio? _androidStudio; - final Platform _platform; /// Builds the AAR and POM files for the current Flutter module or plugin. @override @@ -268,11 +266,7 @@ class AndroidGradleBuilder implements AndroidBuilder { AndroidStudioJavaGradleConflictMigration(_logger, project: project.android, androidStudio: _androidStudio, - fileSystem: _fileSystem, - processUtils: _processUtils, - platform: _platform, - os: globals.os, - androidSdk: globals.androidSdk) + java: globals.java) , ]; @@ -428,7 +422,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ..start(); int exitCode = 1; try { - final String? javaHome = globals.androidSdk?.javaHome; + final String? javaHome = globals.java?.javaHome; exitCode = await _processUtils.stream( command, workingDirectory: project.android.hostAppGradleRoot.path, @@ -698,7 +692,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ..start(); RunResult result; try { - final String? javaHome = globals.androidSdk?.javaHome; + final String? javaHome = globals.java?.javaHome; result = await _processUtils.run( command, workingDirectory: project.android.hostAppGradleRoot.path, @@ -751,7 +745,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ..start(); RunResult result; try { - final String? javaHome = globals.androidSdk?.javaHome; + final String? javaHome = globals.java?.javaHome; result = await _processUtils.run( command, workingDirectory: project.android.hostAppGradleRoot.path, diff --git a/packages/flutter_tools/lib/src/android/java.dart b/packages/flutter_tools/lib/src/android/java.dart new file mode 100644 index 0000000000..365b1eff04 --- /dev/null +++ b/packages/flutter_tools/lib/src/android/java.dart @@ -0,0 +1,230 @@ +// 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/file_system.dart'; +import '../base/logger.dart'; +import '../base/os.dart'; +import '../base/platform.dart'; +import '../base/process.dart'; +import 'android_studio.dart'; + +const String _javaHomeEnvironmentVariable = 'JAVA_HOME'; +const String _kJavaExecutable = '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); + + /// 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. + // 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 Java? find({ + 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( + 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's 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's 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 get environment { + return { + 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 JavaVersion? version = (() { + final RunResult result = _processUtils.runSync( + [binaryPath, '--version'], + environment: environment, + ); + if (result.exitCode != 0) { + _logger.printTrace('java --version failed: exitCode: ${result.exitCode}' + ' stdout: ${result.stdout} stderr: ${result.stderr}'); + } + return JavaVersion.tryParseFromJavaOutput(result.stdout, logger: _logger); + })(); + + bool canRun() { + return _processManager.canRun(binaryPath); + } +} + +String? _findJavaHome({ + required Logger logger, + required AndroidStudio? androidStudio, + required Platform platform, +}) { + final String? androidStudioJavaPath = androidStudio?.javaPath; + if (androidStudioJavaPath != null) { + return androidStudioJavaPath; + } + + final String? javaHomeEnv = platform.environment[_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(_kJavaExecutable)?.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.'; +} + +class JavaVersion { + JavaVersion({ + required this.longText, + required this.number + }); + + /// Typically the first line of the output from `java --version`. + /// For example, `"openjdk 19.0.2 2023-01-17"`. + final String longText; + + /// The version number. For example, `"19.0.2."`. + final String number; + + /// Extracts JDK version from the output of java --version. + static JavaVersion? tryParseFromJavaOutput(String rawVersionOutput, { + required Logger logger, + }) { + final List versionLines = rawVersionOutput.split('\n'); + final String longText = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; + + // 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 matches = + jdkVersionRegex.allMatches(rawVersionOutput); + if (matches.isEmpty) { + logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); + return null; + } + final String? rawShortText = matches.first.group(0); + if (rawShortText == null || rawShortText.split('_').isEmpty) { + logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); + return null; + } + + // Trim away _d+ from versions 1.8 and below. + final String shortText = rawShortText.split('_').first; + + return JavaVersion(longText: longText, number: shortText); + } +} diff --git a/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart index 5788a49aff..63bb6c4940 100644 --- a/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart +++ b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart @@ -5,15 +5,12 @@ import 'package:meta/meta.dart'; import '../../base/file_system.dart'; -import '../../base/os.dart'; -import '../../base/platform.dart'; -import '../../base/process.dart'; import '../../base/project_migrator.dart'; import '../../base/version.dart'; import '../../project.dart'; -import '../android_sdk.dart'; import '../android_studio.dart'; import '../gradle_utils.dart'; +import '../java.dart'; // Android Studio 2022.2 "Flamingo" is the first to bundle a Java 17 JDK. // Previous versions bundled a Java 11 JDK. @@ -77,25 +74,14 @@ class AndroidStudioJavaGradleConflictMigration extends ProjectMigrator { super.logger, {required AndroidProject project, AndroidStudio? androidStudio, - required FileSystem fileSystem, - required ProcessUtils processUtils, - required Platform platform, - required OperatingSystemUtils os, - AndroidSdk? androidSdk, + required Java? java, }) : _gradleWrapperPropertiesFile = getGradleWrapperFile(project.hostAppGradleRoot), _androidStudio = androidStudio, - _fileSystem = fileSystem, - _processUtils = processUtils, - _platform = platform, - _os = os, - _androidSdk = androidSdk; + _java = java; + final File _gradleWrapperPropertiesFile; final AndroidStudio? _androidStudio; - final FileSystem _fileSystem; - final ProcessUtils _processUtils; - final Platform _platform; - final OperatingSystemUtils _os; - final AndroidSdk? _androidSdk; + final Java? _java; @override void migrate() { @@ -113,13 +99,7 @@ class AndroidStudioJavaGradleConflictMigration extends ProjectMigrator { return; } - final String? javaVersionString = _androidSdk?.getJavaVersion( - androidStudio: _androidStudio, - fileSystem: _fileSystem, - operatingSystemUtils: _os, - platform: _platform, - processUtils: _processUtils, - ); + final String? javaVersionString = _java?.version?.number; final Version? javaVersion = Version.parse(javaVersionString); if (javaVersion == null) { logger.printTrace(javaVersionNotFound); diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 66db800266..5791cd0216 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -1360,6 +1360,7 @@ class EmulatorDomain extends Domain { EmulatorManager emulators = EmulatorManager( fileSystem: globals.fs, logger: globals.logger, + java: globals.java, androidSdk: globals.androidSdk, processManager: globals.processManager, androidWorkflow: androidWorkflow!, diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 3136024b54..85e69809a0 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -14,6 +14,7 @@ import 'android/android_studio.dart'; import 'android/android_workflow.dart'; import 'android/gradle.dart'; import 'android/gradle_utils.dart'; +import 'android/java.dart'; import 'application_package.dart'; import 'artifacts.dart'; import 'asset.dart'; @@ -97,11 +98,11 @@ Future runInContext( androidStudio: globals.androidStudio, ), AndroidLicenseValidator: () => AndroidLicenseValidator( - operatingSystemUtils: globals.os, platform: globals.platform, userMessages: globals.userMessages, processManager: globals.processManager, androidStudio: globals.androidStudio, + java: globals.java, androidSdk: globals.androidSdk, logger: globals.logger, fileSystem: globals.fs, @@ -216,6 +217,7 @@ Future runInContext( Doctor: () => Doctor(logger: globals.logger), DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, EmulatorManager: () => EmulatorManager( + java: globals.java, androidSdk: globals.androidSdk, processManager: globals.processManager, logger: globals.logger, @@ -253,6 +255,13 @@ Future runInContext( xcode: globals.xcode!, platform: globals.platform, ), + Java: () => Java.find( + androidStudio: globals.androidStudio, + logger: globals.logger, + fileSystem: globals.fs, + platform: globals.platform, + processManager: globals.processManager + ), LocalEngineLocator: () => LocalEngineLocator( userMessages: userMessages, logger: globals.logger, diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart index bcfe2d091c..e210d2ac94 100644 --- a/packages/flutter_tools/lib/src/emulator.dart +++ b/packages/flutter_tools/lib/src/emulator.dart @@ -10,6 +10,7 @@ import 'package:process/process.dart'; import 'android/android_emulator.dart'; import 'android/android_sdk.dart'; import 'android/android_workflow.dart'; +import 'android/java.dart'; import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; @@ -22,12 +23,14 @@ EmulatorManager? get emulatorManager => context.get(); /// A class to get all available emulators. class EmulatorManager { EmulatorManager({ + required Java? java, AndroidSdk? androidSdk, required Logger logger, required ProcessManager processManager, required AndroidWorkflow androidWorkflow, required FileSystem fileSystem, - }) : _androidSdk = androidSdk, + }) : _java = java, + _androidSdk = androidSdk, _processUtils = ProcessUtils(logger: logger, processManager: processManager), _androidEmulators = AndroidEmulators( androidSdk: androidSdk, @@ -39,6 +42,7 @@ class EmulatorManager { _emulatorDiscoverers.add(_androidEmulators); } + final Java? _java; final AndroidSdk? _androidSdk; final AndroidEmulators _androidEmulators; final ProcessUtils _processUtils; @@ -152,7 +156,7 @@ class EmulatorManager { '-n', emulatorName, '-k', sdkId, '-d', device, - ], environment: _androidSdk?.sdkManagerEnv, + ], environment: _java?.environment, ); return CreateEmulatorResult( emulatorName, @@ -175,7 +179,7 @@ class EmulatorManager { '-c', ]; final RunResult runResult = await _processUtils.run(args, - environment: _androidSdk?.sdkManagerEnv); + environment: _java?.environment); if (runResult.exitCode != 0) { return null; } @@ -205,7 +209,7 @@ class EmulatorManager { '-n', 'temp', ]; final RunResult runResult = await _processUtils.run(args, - environment: _androidSdk?.sdkManagerEnv); + environment: _java?.environment); // Get the list of IDs that match our criteria final List availableIDs = runResult.stderr diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 4ae5f9a4e5..bf3571bec5 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -9,6 +9,7 @@ import 'package:unified_analytics/unified_analytics.dart'; import 'android/android_sdk.dart'; import 'android/android_studio.dart'; import 'android/gradle_utils.dart'; +import 'android/java.dart'; import 'artifacts.dart'; import 'base/bot_detector.dart'; import 'base/config.dart'; @@ -308,3 +309,9 @@ final RegExp kVMServiceMessageRegExp = RegExp(r'The Dart VM service is listening // The official tool no longer allows non-null safe builds. This can be // overridden in other clients. NonNullSafeBuilds get nonNullSafeBuilds => context.get() ?? NonNullSafeBuilds.notAllowed; + +/// Contains information about the JRE/JDK to use for Java-dependent operations. +/// +/// A value of [null] indicates that no installation of java could be found on +/// the host machine. +Java? get java => context.get(); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index ac70d0f318..d726192f53 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -568,13 +568,7 @@ class AndroidProject extends FlutterProjectPlatform { hostAppGradleRoot, globals.logger, globals.processManager); final String? agpVersion = gradle.getAgpVersion(hostAppGradleRoot, globals.logger); - final String? javaVersion = globals.androidSdk?.getJavaVersion( - androidStudio: globals.androidStudio, - fileSystem: globals.fs, - operatingSystemUtils: globals.os, - platform: globals.platform, - processUtils: globals.processUtils, - ); + final String? javaVersion = globals.java?.version?.number; // Assume valid configuration. String description = validJavaGradleAgpString; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart index 9a61faecbd..9dbfa3ce9a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_device.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -20,6 +21,7 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_devices.dart'; +import '../../src/fakes.dart'; void main() { Daemon? daemon; @@ -104,6 +106,8 @@ void main() { final bool supportsRuntimeMode = await device.supportsRuntimeMode(BuildMode.release); expect(fakeDevice.supportsRuntimeModeCalledBuildMode, BuildMode.release); expect(supportsRuntimeMode, true); + }, overrides: { + Java: () => FakeJava(), }); testUsingContext('redirects logs', () async { @@ -183,6 +187,7 @@ void main() { expect(fakeDevice.stopAppPackage, applicationPackage); expect(stopAppResult, true); }, overrides: { + Java: () => FakeJava(), ApplicationPackageFactory: () => applicationPackageFactory, FileSystem: () => memoryFileSystem, ProcessManager: () => fakeProcessManager, @@ -212,6 +217,7 @@ void main() { expect(await screenshotOutputFile.readAsBytes(), screenshot); }, overrides: { + Java: () => FakeJava(), FileSystem: () => memoryFileSystem, ProcessManager: () => fakeProcessManager, }); diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart index 3963abd709..515c3a7d9f 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart @@ -447,9 +447,6 @@ class FakeAndroidSdk extends Fake implements AndroidSdk { @override final Directory directory; - - @override - String? get javaHome => 'java'; } class FakeAndroidStudio extends Fake implements AndroidStudio { diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_appbundle_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_appbundle_test.dart index bb9bd2dd70..0bd3eaa08a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_appbundle_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_appbundle_test.dart @@ -230,7 +230,4 @@ class FakeAndroidSdk extends Fake implements AndroidSdk { @override final Directory directory; - - @override - String? get javaHome => 'java'; } diff --git a/packages/flutter_tools/test/general.shard/analytics_test.dart b/packages/flutter_tools/test/general.shard/analytics_test.dart index 8c37125eb2..b8bfd57a47 100644 --- a/packages/flutter_tools/test/general.shard/analytics_test.dart +++ b/packages/flutter_tools/test/general.shard/analytics_test.dart @@ -389,8 +389,6 @@ class FakeDoctor extends Fake implements Doctor { } } -class FakeAndroidStudio extends Fake implements AndroidStudio {} - class FakeClock extends Fake implements SystemClock { List times = []; diff --git a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart index cd4de5875a..c83a2b12a8 100644 --- a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart @@ -4,9 +4,9 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; -import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/android/migrations/android_studio_java_gradle_conflict_migration.dart'; import 'package:flutter_tools/src/android/migrations/top_level_gradle_build_file_migration.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -19,6 +19,7 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; const String otherGradleVersionWrapper = r''' distributionBase=GRADLE_USER_HOME @@ -46,6 +47,9 @@ zipStorePath=wrapper/dists final Version androidStudioDolphin = Version(2021, 3, 1); +final JavaVersion _javaVersion17 = JavaVersion(longText: 'openjdk 17.0.2', number: '17.0.2'); +final JavaVersion _javaVersion16 = JavaVersion(longText: 'openjdk 16.0.2', number: '16.0.2'); + void main() { group('Android migration', () { group('migrate the Gradle "clean" task to lazy declaration', () { @@ -136,14 +140,10 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if files are missing', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioDolphin), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); migration.migrate(); expect(gradleWrapperPropertiesFile.existsSync(), isFalse); @@ -153,13 +153,9 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if android studio is null', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -170,14 +166,10 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if android studio version is null', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: null), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -188,14 +180,10 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if error is encountered in migrate()', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeErroringJava(), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeErroringAndroidSdk(), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -206,14 +194,10 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if android studio version is less than flamingo', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioDolphin), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -223,14 +207,10 @@ tasks.register("clean", Delete) { testWithoutContext('skipped if bundled java version is less than 17', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion16), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '16'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -241,14 +221,10 @@ tasks.register("clean", Delete) { testWithoutContext('nothing is changed if gradle version not one that was ' 'used by flutter create', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(otherGradleVersionWrapper); migration.migrate(); @@ -259,14 +235,10 @@ tasks.register("clean", Delete) { testWithoutContext('change is made with one of the specific gradle versions' ' we migrate for', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); migration.migrate(); @@ -278,14 +250,10 @@ tasks.register("clean", Delete) { testWithoutContext('change is not made when opt out flag is set', () { final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + java: FakeJava(version: _javaVersion17), bufferLogger, project: project, androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), - fileSystem: FakeFileSystem(), - processUtils: FakeProcessUtils(), - platform: FakePlatform(), - os: FakeOperatingSystemUtils(), - androidSdk: FakeAndroidSdk(javaVersion: '17'), ); gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate + optOutFlag); migration.migrate(); @@ -314,37 +282,10 @@ class FakeAndroidStudio extends Fake implements AndroidStudio { Version? get version => _version; } -class FakeAndroidSdk extends Fake implements AndroidSdk { - FakeAndroidSdk({required String javaVersion}) { - _javaVersion = javaVersion; - } - - late String _javaVersion; - +class FakeErroringJava extends FakeJava { @override - String? getJavaVersion({ - required AndroidStudio? androidStudio, - required FileSystem fileSystem, - required OperatingSystemUtils operatingSystemUtils, - required Platform platform, - required ProcessUtils processUtils, - }) { - return _javaVersion; - } -} - -class FakeErroringAndroidSdk extends Fake implements AndroidSdk { - FakeErroringAndroidSdk(); - - @override - String? getJavaVersion({ - required AndroidStudio? androidStudio, - required FileSystem fileSystem, - required OperatingSystemUtils operatingSystemUtils, - required Platform platform, - required ProcessUtils processUtils, - }) { - throw const FileSystemException(); + JavaVersion get version { + throw Exception('How did this happen?'); } } diff --git a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart index 5f950645e5..a82e62d32b 100644 --- a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart @@ -5,19 +5,16 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:test/fake.dart'; -import '../../integration.shard/test_utils.dart'; import '../../src/common.dart'; import '../../src/context.dart'; -import '../../src/fakes.dart' show FakeAndroidStudio, FakeOperatingSystemUtils; void main() { late MemoryFileSystem fileSystem; @@ -354,18 +351,18 @@ void main() { final String androidStudioBundledJdkHome = globals.androidStudio!.javaPath!; final String expectedJavaBinaryPath = globals.fs.path.join(androidStudioBundledJdkHome, 'bin', 'java'); - final String? foundJavaBinaryPath = AndroidSdk.findJavaBinary( + final String? foundJavaBinaryPath = Java.find( + logger: globals.logger, androidStudio: globals.androidStudio, fileSystem: globals.fs, - operatingSystemUtils: globals.os, platform: globals.platform, - ); + processManager: globals.processManager, + )?.binaryPath; expect(foundJavaBinaryPath, expectedJavaBinaryPath); }, overrides: { FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), - OperatingSystemUtils: () => FakeOperatingSystemUtilsWithJava(), Platform: () => FakePlatform(), Config: () => Config, AndroidStudio: () => FakeAndroidStudioWithJdk(), @@ -374,18 +371,18 @@ void main() { testUsingContext('returns the current value of JAVA_HOME if it is set and the JDK bundled with Android Studio could not be found', () { final String expectedJavaBinaryPath = globals.fs.path.join('java-home-path', 'bin', 'java'); - final String? foundJavaBinaryPath = AndroidSdk.findJavaBinary( + final String? foundJavaBinaryPath = Java.find( + logger: globals.logger, androidStudio: globals.androidStudio, fileSystem: globals.fs, - operatingSystemUtils: globals.os, platform: globals.platform, - ); + processManager: globals.processManager, + )?.binaryPath; expect(foundJavaBinaryPath, expectedJavaBinaryPath); }, overrides: { FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.any(), - OperatingSystemUtils: () => FakeOperatingSystemUtilsWithJava(), + ProcessManager: () => FakeProcessManager.empty(), Platform: () => FakePlatform(environment: { AndroidSdk.javaHomeEnvironmentVariable: 'java-home-path', }), @@ -394,168 +391,54 @@ void main() { }); testUsingContext('returns the java binary found on PATH if no other can be found', () { - final String? foundJavaBinaryPath = AndroidSdk.findJavaBinary( + final String? foundJavaBinaryPath = Java.find( + logger: globals.logger, androidStudio: globals.androidStudio, fileSystem: globals.fs, - operatingSystemUtils: globals.os, platform: globals.platform, - ); + processManager: globals.processManager, + )?.binaryPath; - expect(foundJavaBinaryPath, globals.os.which('java')!.path); + expect(foundJavaBinaryPath, 'java'); }, overrides: { + Logger: () => BufferLogger.test(), FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.any(), - OperatingSystemUtils: () => FakeOperatingSystemUtilsWithJava(), + ProcessManager: () => FakeProcessManager.list([ + const FakeCommand( + command: ['which', 'java'], + stdout: 'java', + ), + ]), Platform: () => FakePlatform(), Config: () => Config, AndroidStudio: () => FakeAndroidStudioWithoutJdk(), }); testUsingContext('returns null if no java binary could be found', () { - final String? foundJavaBinaryPath = AndroidSdk.findJavaBinary( + final String? foundJavaBinaryPath = Java.find( + logger: globals.logger, androidStudio: globals.androidStudio, fileSystem: globals.fs, - operatingSystemUtils: globals.os, platform: globals.platform, - ); + processManager: globals.processManager, + )?.binaryPath; expect(foundJavaBinaryPath, null); }, overrides: { + Logger: () => BufferLogger.test(), FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.any(), - OperatingSystemUtils: () => FakeOperatingSystemUtilsWithoutJava(), + ProcessManager: () => FakeProcessManager.list([ + const FakeCommand( + command: ['which', 'java'], + exitCode: 1, + ), + ]), Platform: () => FakePlatform(), Config: () => Config, AndroidStudio: () => FakeAndroidStudioWithoutJdk(), }); }); }); - - group('java version', () { - const String exampleJdk8Output = ''' -java version "1.8.0_202" -Java(TM) SE Runtime Environment (build 1.8.0_202-b10) -Java HotSpot(TM) 64-Bit Server VM (build 25.202-b10, mixed mode) -'''; - // Example strings came from actual terminal output. - testWithoutContext('parses jdk 8', () { - expect(AndroidSdk.parseJavaVersion(exampleJdk8Output), '1.8.0'); - }); - - testWithoutContext('parses jdk 11 windows', () { - const String exampleJdkOutput = ''' -java version "11.0.14" -Java(TM) SE Runtime Environment (build 11.0.14+10-b13) -Java HotSpot(TM) 64-Bit Server VM (build 11.0.14+10-b13, mixed mode) -'''; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '11.0.14'); - }); - - testWithoutContext('parses jdk 11 mac/linux', () { - const String exampleJdkOutput = ''' -openjdk version "11.0.18" 2023-01-17 LTS -OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) -OpenJDK 64-Bit Server VM Zulu11.62+17-CA (build 11.0.18+10-LTS, mixed mode) -'''; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '11.0.18'); - }); - - testWithoutContext('parses jdk 17', () { - const String exampleJdkOutput = ''' -openjdk 17.0.6 2023-01-17 -OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) -OpenJDK 64-Bit Server VM (build 17.0.6+0-17.0.6b802.4-9586694, mixed mode) -'''; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '17.0.6'); - }); - - testWithoutContext('parses jdk 19', () { - const String exampleJdkOutput = ''' -openjdk 19.0.2 2023-01-17 -OpenJDK Runtime Environment Homebrew (build 19.0.2) -OpenJDK 64-Bit Server VM Homebrew (build 19.0.2, mixed mode, sharing) -'''; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '19.0.2'); - }); - - // https://chrome-infra-packages.appspot.com/p/flutter/java/openjdk/ - testWithoutContext('parses jdk output from ci', () { - const String exampleJdkOutput = ''' -openjdk 11.0.2 2019-01-15 -OpenJDK Runtime Environment 18.9 (build 11.0.2+9) -OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode) -'''; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '11.0.2'); - }); - - testWithoutContext('parses jdk two number versions', () { - const String exampleJdkOutput = 'openjdk 19.0 2023-01-17'; - expect(AndroidSdk.parseJavaVersion(exampleJdkOutput), '19.0'); - }); - - testUsingContext('getJavaBinary with AS install', () { - final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem); - config.setValue('android-sdk', sdkDir.path); - final AndroidStudio androidStudio = FakeAndroidStudio(); - - final String javaPath = AndroidSdk.findJavaBinary( - androidStudio: androidStudio, - fileSystem: fileSystem, - operatingSystemUtils: FakeOperatingSystemUtils(), - platform: platform)!; - // Built from the implementation of findJavaBinary android studio case. - final String expectedJavaPath = '${androidStudio.javaPath}/bin/java'; - - expect(javaPath, expectedJavaPath); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - Config: () => config, - Platform: () => FakePlatform(environment: {}), - }); - - group('java', () { - late AndroidStudio androidStudio; - setUp(() { - androidStudio = FakeAndroidStudio(); - }); - testUsingContext('getJavaVersion finds AS java and parses version', () { - final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem); - config.setValue('android-sdk', sdkDir.path); - - final ProcessUtils processUtils = ProcessUtils( - processManager: processManager, logger: BufferLogger.test()); - // Built from the implementation of findJavaBinary android studio case. - final String expectedJavaPath = '${androidStudio.javaPath}/bin/java'; - - processManager.addCommand(FakeCommand( - command: [ - expectedJavaPath, - '--version', - ], - stdout: exampleJdk8Output, - )); - - final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; - - final String? javaVersion = sdk.getJavaVersion( - androidStudio: androidStudio, - fileSystem: fileSystem, - operatingSystemUtils: FakeOperatingSystemUtils(), - platform: FakePlatform(), - processUtils: processUtils, - ); - - expect(javaVersion, '1.8.0'); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => processManager, - AndroidStudio: () => androidStudio, - Config: () => config, - Platform: () => FakePlatform(environment: {}), - }); - }); - }); } /// A broken SDK installation. @@ -646,20 +529,3 @@ class FakeAndroidStudioWithoutJdk extends Fake implements AndroidStudio { @override String? get javaPath => null; } - -class FakeOperatingSystemUtilsWithJava extends Fake implements OperatingSystemUtils { - @override - File? which(String execName) { - if (execName == 'java') { - return globals.fs.file('/fake/which/java/path'); - } - return null; - } -} - -class FakeOperatingSystemUtilsWithoutJava extends Fake implements OperatingSystemUtils { - @override - File? which(String execName) { - return null; - } -} diff --git a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart index c43fa131da..c8d6a4dad2 100644 --- a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart @@ -122,6 +122,7 @@ void main() { sdk.sdkManagerPath = '/foo/bar/sdkmanager'; processManager.excludedExecutables.add('/foo/bar/sdkmanager'); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -130,7 +131,6 @@ void main() { logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; @@ -141,6 +141,7 @@ void main() { sdk.sdkManagerPath = '/foo/bar/sdkmanager'; processManager.excludedExecutables.add('/foo/bar/sdkmanager'); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -149,7 +150,6 @@ void main() { logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; @@ -165,6 +165,7 @@ void main() { ], stdout: 'asdasassad', )); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -173,7 +174,6 @@ void main() { logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -194,6 +194,7 @@ All SDK package licenses accepted. )); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -202,7 +203,6 @@ All SDK package licenses accepted. logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -224,6 +224,7 @@ Review licenses that have not been accepted (y/N)? )); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -232,7 +233,6 @@ Review licenses that have not been accepted (y/N)? logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -254,6 +254,7 @@ Review licenses that have not been accepted (y/N)? )); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -262,7 +263,6 @@ Review licenses that have not been accepted (y/N)? logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -280,6 +280,7 @@ Review licenses that have not been accepted (y/N)? )); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -288,7 +289,6 @@ Review licenses that have not been accepted (y/N)? logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); expect(await licenseValidator.runLicenseManager(), isTrue); @@ -299,6 +299,7 @@ Review licenses that have not been accepted (y/N)? processManager.excludedExecutables.add('/foo/bar/sdkmanager'); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -307,7 +308,6 @@ Review licenses that have not been accepted (y/N)? logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); expect(licenseValidator.runLicenseManager(), throwsToolExit()); @@ -328,6 +328,7 @@ Review licenses that have not been accepted (y/N)? final BufferLogger logger = BufferLogger.test(); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -336,7 +337,6 @@ Review licenses that have not been accepted (y/N)? logger: logger, userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); await licenseValidator.runLicenseManager(); @@ -349,6 +349,7 @@ Review licenses that have not been accepted (y/N)? processManager.excludedExecutables.add('/foo/bar/sdkmanager'); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -357,7 +358,6 @@ Review licenses that have not been accepted (y/N)? logger: BufferLogger.test(), userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); expect(licenseValidator.runLicenseManager(), throwsToolExit()); @@ -376,6 +376,7 @@ Review licenses that have not been accepted (y/N)? ); final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( + java: FakeJava(), androidSdk: sdk, fileSystem: fileSystem, processManager: processManager, @@ -384,7 +385,6 @@ Review licenses that have not been accepted (y/N)? logger: logger, userMessages: UserMessages(), androidStudio: FakeAndroidStudio(), - operatingSystemUtils: FakeOperatingSystemUtils(), ); await expectLater( @@ -620,9 +620,6 @@ class FakeAndroidSdk extends Fake implements AndroidSdk { @override List validateSdkWellFormed() => []; - - @override - Map get sdkManagerEnv => {}; } class FakeAndroidSdkVersion extends Fake implements AndroidSdkVersion { diff --git a/packages/flutter_tools/test/general.shard/android/java_test.dart b/packages/flutter_tools/test/general.shard/android/java_test.dart new file mode 100644 index 0000000000..b9c4b42f96 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/android/java_test.dart @@ -0,0 +1,254 @@ +// 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:file/memory.dart'; +import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/java.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:test/fake.dart'; +import 'package:webdriver/async_io.dart'; + +import '../../integration.shard/test_utils.dart'; +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/fakes.dart'; + +void main() { + + late Logger logger; + late FileSystem fs; + late Platform platform; + late FakeProcessManager processManager; + + setUp(() { + logger = BufferLogger.test(); + fs = MemoryFileSystem.test(); + platform = FakePlatform(environment: { + 'PATH': '', + }); + processManager = FakeProcessManager.empty(); + }); + + group(Java, () { + + group('find', () { + testWithoutContext('finds the JDK bundled with Android Studio, if it exists', () { + final AndroidStudio androidStudio = _FakeAndroidStudioWithJdk(); + final String androidStudioBundledJdkHome = androidStudio.javaPath!; + final String expectedJavaBinaryPath = fs.path.join(androidStudioBundledJdkHome, 'bin', 'java'); + + processManager.addCommand(FakeCommand( + command: [ + expectedJavaBinaryPath, + '--version', + ], + stdout: ''' +openjdk 19.0.2 2023-01-17 +OpenJDK Runtime Environment Zulu19.32+15-CA (build 19.0.2+7) +OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) +''' + )); + final Java java = Java.find( + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: platform, + processManager: processManager, + )!; + final JavaVersion version = java.version!; + + expect(java.javaHome, androidStudioBundledJdkHome); + expect(java.binaryPath, expectedJavaBinaryPath); + + expect(version.longText, 'OpenJDK Runtime Environment Zulu19.32+15-CA (build 19.0.2+7)'); + expect(version.number, '19.0.2'); + }); + + testWithoutContext('finds JAVA_HOME if it is set and the JDK bundled with Android Studio could not be found', () { + final AndroidStudio androidStudio = _FakeAndroidStudioWithoutJdk(); + const String javaHome = '/java/home'; + final String expectedJavaBinaryPath = fs.path.join(javaHome, 'bin', 'java'); + + final Java java = Java.find( + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: FakePlatform(environment: { + 'JAVA_HOME': javaHome, + }), + processManager: processManager, + )!; + + expect(java.javaHome, javaHome); + expect(java.binaryPath, expectedJavaBinaryPath); + }); + + testWithoutContext('returns the java binary found on PATH if no other can be found', () { + final AndroidStudio androidStudio = _FakeAndroidStudioWithoutJdk(); + final OperatingSystemUtils os = _FakeOperatingSystemUtilsWithJava(fileSystem); + + processManager.addCommand( + const FakeCommand( + command: ['which', 'java'], + stdout: '/fake/which/java/path', + ), + ); + + final Java java = Java.find( + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: platform, + processManager: processManager, + )!; + + expect(java.javaHome, isNull); + expect(java.binaryPath, os.which('java')!.path); + }); + + testWithoutContext('returns null if no java could be found', () { + final AndroidStudio androidStudio = _FakeAndroidStudioWithoutJdk(); + processManager.addCommand( + const FakeCommand( + command: ['which', 'java'], + exitCode: 1, + ), + ); + final Java? java = Java.find( + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: platform, + processManager: processManager, + ); + expect(java, isNull); + }); + }); + + group('getVersionString', () { + late Java java; + + setUp(() { + processManager = FakeProcessManager.empty(); + java = Java( + fileSystem: fs, + logger: logger, + os: FakeOperatingSystemUtils(), + platform: platform, + processManager: processManager, + binaryPath: 'javaHome/bin/java', + javaHome: 'javaHome', + ); + }); + + void addJavaVersionCommand(String output) { + processManager.addCommand( + FakeCommand( + command: [java.binaryPath, '--version'], + stdout: output, + ), + ); + } + + testWithoutContext('parses jdk 8', () { + addJavaVersionCommand(''' +java version "1.8.0_202" +Java(TM) SE Runtime Environment (build 1.8.0_202-b10) +Java HotSpot(TM) 64-Bit Server VM (build 25.202-b10, mixed mode) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'Java(TM) SE Runtime Environment (build 1.8.0_202-b10)'); + expect(version.number, '1.8.0'); + }); + testWithoutContext('parses jdk 11 windows', () { + addJavaVersionCommand(''' +java version "11.0.14" +Java(TM) SE Runtime Environment (build 11.0.14+10-b13) +Java HotSpot(TM) 64-Bit Server VM (build 11.0.14+10-b13, mixed mode) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'Java(TM) SE Runtime Environment (build 11.0.14+10-b13)'); + expect(version.number, '11.0.14'); + }); + + testWithoutContext('parses jdk 11 mac/linux', () { + addJavaVersionCommand(''' +openjdk version "11.0.18" 2023-01-17 LTS +OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) +OpenJDK 64-Bit Server VM Zulu11.62+17-CA (build 11.0.18+10-LTS, mixed mode) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)'); + expect(version.number, '11.0.18'); + }); + + testWithoutContext('parses jdk 17', () { + addJavaVersionCommand(''' +openjdk 17.0.6 2023-01-17 +OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) +OpenJDK 64-Bit Server VM (build 17.0.6+0-17.0.6b802.4-9586694, mixed mode) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)'); + expect(version.number, '17.0.6'); + }); + + testWithoutContext('parses jdk 19', () { + addJavaVersionCommand(''' +openjdk 19.0.2 2023-01-17 +OpenJDK Runtime Environment Homebrew (build 19.0.2) +OpenJDK 64-Bit Server VM Homebrew (build 19.0.2, mixed mode, sharing) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'OpenJDK Runtime Environment Homebrew (build 19.0.2)'); + expect(version.number, '19.0.2'); + }); + + // https://chrome-infra-packages.appspot.com/p/flutter/java/openjdk/ + testWithoutContext('parses jdk output from ci', () { + addJavaVersionCommand(''' +openjdk 11.0.2 2019-01-15 +OpenJDK Runtime Environment 18.9 (build 11.0.2+9) +OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode) +'''); + final JavaVersion version = java.version!; + expect(version.longText, 'OpenJDK Runtime Environment 18.9 (build 11.0.2+9)'); + expect(version.number, '11.0.2'); + }); + + testWithoutContext('parses jdk two number versions', () { + addJavaVersionCommand('openjdk 19.0 2023-01-17'); + final JavaVersion version = java.version!; + expect(version.longText, 'openjdk 19.0 2023-01-17'); + expect(version.number, '19.0'); + }); + }); + }); +} + +class _FakeAndroidStudioWithJdk extends Fake implements AndroidStudio { + @override + String? get javaPath => '/fake/android_studio/java/path/'; +} + +class _FakeAndroidStudioWithoutJdk extends Fake implements AndroidStudio { + @override + String? get javaPath => null; +} + +class _FakeOperatingSystemUtilsWithJava extends FakeOperatingSystemUtils { + _FakeOperatingSystemUtilsWithJava(this._fileSystem); + + final FileSystem _fileSystem; + @override + File? which(String execName) { + if (execName == 'java') { + return _fileSystem.file('/fake/which/java/path'); + } + throw const InvalidArgumentException(null, null); + } +} diff --git a/packages/flutter_tools/test/general.shard/emulator_test.dart b/packages/flutter_tools/test/general.shard/emulator_test.dart index 16afc86f0e..1e54f5b774 100644 --- a/packages/flutter_tools/test/general.shard/emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/emulator_test.dart @@ -67,6 +67,7 @@ void main() { testUsingContext('getEmulators', () async { // Test that EmulatorManager.getEmulators() doesn't throw. final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -89,6 +90,7 @@ void main() { testUsingContext('getEmulators with no Android SDK', () async { // Test that EmulatorManager.getEmulators() doesn't throw when there's no Android SDK. final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -109,6 +111,7 @@ void main() { testWithoutContext('getEmulatorsById', () async { final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators, + java: FakeJava(), logger: BufferLogger.test(), processManager: fakeProcessManager, androidWorkflow: AndroidWorkflow( @@ -129,6 +132,7 @@ void main() { testUsingContext('create emulator with a missing avdmanager does not crash.', () async { sdk.avdManagerPath = null; final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -152,6 +156,7 @@ void main() { // iOS discovery uses context. testUsingContext('create emulator with an empty name does not fail', () async { final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -191,6 +196,7 @@ void main() { testWithoutContext('create emulator with a unique name does not throw', () async { final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -227,6 +233,7 @@ void main() { testWithoutContext('create emulator with an existing name errors', () async { final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -266,6 +273,7 @@ void main() { // iOS discovery uses context. testUsingContext('create emulator without a name but when default exists adds a suffix', () async { final EmulatorManager emulatorManager = EmulatorManager( + java: FakeJava(), fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ @@ -342,6 +350,7 @@ void main() { class TestEmulatorManager extends EmulatorManager { TestEmulatorManager(this.allEmulators, { + required super.java, required super.logger, required super.processManager, required super.androidWorkflow, @@ -393,7 +402,4 @@ class FakeAndroidSdk extends Fake implements AndroidSdk { @override String getAvdPath() => 'avd'; - - @override - Map get sdkManagerEnv => {}; } diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 3c598427bd..2cad7162ba 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -7,11 +7,10 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart' as gradle_utils; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; -import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/convert.dart'; @@ -407,9 +406,7 @@ void main() { }); group('java gradle agp compatibility', () { - Future configureJavaGradleAgpForTest( - FakeAndroidSdkWithDir androidSdk, { - required String javaV, + Future configureGradleAgpForTest({ required String gradleV, required String agpV, }) async { @@ -422,7 +419,6 @@ dependencies { '''; }); addGradleWrapperFile(project.directory, gradleV); - androidSdk.javaVersion = javaV; return project; } @@ -431,22 +427,22 @@ dependencies { // especially important for filesystem. group('_', () { final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); + java = FakeJava(version: JavaVersion(longText: '17.0.2', number: '17.0.2')); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'flamingo values are compatible', () async { - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: '17.0.2', + final FlutterProject? project = await configureGradleAgpForTest( gradleV: '8.0', agpV: '7.4.2', ); @@ -454,6 +450,7 @@ dependencies { await project!.android.hasValidJavaGradleAgpVersions(); expect(value.success, isTrue); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, @@ -461,22 +458,22 @@ dependencies { }); group('_', () { final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); + java = FakeJava(version: JavaVersion(longText: '1.8.0_242', number: '1.8.0_242')); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'java 8 era values are compatible', () async { - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: '1.8.0_242', + final FlutterProject? project = await configureGradleAgpForTest( gradleV: '6.7.1', agpV: '4.2.0', ); @@ -484,6 +481,7 @@ dependencies { await project!.android.hasValidJavaGradleAgpVersions(); expect(value.success, isTrue); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, @@ -492,22 +490,22 @@ dependencies { group('_', () { final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); + java = FakeJava(version: JavaVersion(longText: '11.0.14', number: '11.0.14')); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'electric eel era values are compatible', () async { - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: '11.0.14', + final FlutterProject? project = await configureGradleAgpForTest( gradleV: '7.3.3', agpV: '7.2.0', ); @@ -515,32 +513,35 @@ dependencies { await project!.android.hasValidJavaGradleAgpVersions(); expect(value.success, isTrue); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, ); }); group('_', () { + const String javaV = '17.0.2'; + const String gradleV = '6.7.3'; + const String agpV = '7.2.0'; + final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); + java = FakeJava(version: JavaVersion(longText: javaV, number: javaV)); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'incompatible everything', () async { - const String javaV = '17.0.2'; - const String gradleV = '6.7.3'; - const String agpV = '7.2.0'; - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: javaV, + + final FlutterProject? project = await configureGradleAgpForTest( gradleV: gradleV, agpV: agpV, ); @@ -563,32 +564,34 @@ dependencies { expect(value.description, contains(RegExp(javaV))); expect(value.description, contains(RegExp(gradleV))); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, ); }); group('_', () { + const String javaV = '17.0.2'; + const String gradleV = '6.7.3'; + const String agpV = '4.2.0'; + final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); + java = FakeJava(version: JavaVersion(longText: '17.0.2', number: '17.0.2')); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'incompatible java/gradle only', () async { - const String javaV = '17.0.2'; - const String gradleV = '6.7.3'; - const String agpV = '4.2.0'; - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: javaV, + final FlutterProject? project = await configureGradleAgpForTest( gradleV: gradleV, agpV: agpV, ); @@ -606,6 +609,7 @@ dependencies { expect(value.description, contains(RegExp(javaV))); expect(value.description, contains(RegExp(gradleV))); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, @@ -613,25 +617,24 @@ dependencies { }); group('_', () { final FakeProcessManager processManager; + final Java java; final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); + java = FakeJava(version: JavaVersion(longText: '11.0.2', number: '11.0.2')); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = - FakeAndroidSdkWithDir(fileSystem.currentDirectory, androidStudio); + FakeAndroidSdkWithDir(fileSystem.currentDirectory); fileSystem.currentDirectory .childDirectory(androidStudio.javaPath!) .createSync(); _testInMemory( 'incompatible gradle/agp only', () async { - const String javaV = '11.0.2'; const String gradleV = '7.0.3'; const String agpV = '7.1.0'; - final FlutterProject? project = await configureJavaGradleAgpForTest( - androidSdk, - javaV: javaV, + final FlutterProject? project = await configureGradleAgpForTest( gradleV: gradleV, agpV: agpV, ); @@ -649,6 +652,7 @@ dependencies { expect(value.description, contains(RegExp(gradleV))); expect(value.description, contains(RegExp(agpV))); }, + java: java, androidStudio: androidStudio, processManager: processManager, androidSdk: androidSdk, @@ -1404,6 +1408,7 @@ void _testInMemory( String description, Future Function() testMethod, { FileSystem? fileSystem, + Java? java, AndroidStudio? androidStudio, ProcessManager? processManager, AndroidSdk? androidSdk, @@ -1466,6 +1471,7 @@ void _testInMemory( overrides: { FileSystem: () => testFileSystem, ProcessManager: () => processManager ?? FakeProcessManager.any(), + Java : () => java, AndroidStudio: () => androidStudio ?? FakeAndroidStudio(), // Intentionlly null if not set. Some ios tests fail if this is a fake. AndroidSdk: () => androidSdk, @@ -1667,37 +1673,10 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete } class FakeAndroidSdkWithDir extends Fake implements AndroidSdk { - FakeAndroidSdkWithDir(this._directory, AndroidStudio _androidStudio) { - _javaPath = '${_androidStudio.javaPath}/bin/java'; - } - late String _javaPath; - String? javaVersion; + FakeAndroidSdkWithDir(this._directory); final Directory _directory; - @override - late bool platformToolsAvailable; - - @override - late bool licensesAvailable; - - @override - AndroidSdkVersion? latestVersion; - @override Directory get directory => _directory; - - @override - Map get sdkManagerEnv => {'PATH': _javaPath}; - - @override - String? getJavaVersion({ - required AndroidStudio? androidStudio, - required FileSystem fileSystem, - required OperatingSystemUtils operatingSystemUtils, - required Platform platform, - required ProcessUtils processUtils, - }) { - return javaVersion; - } } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index 8a54b1a4c6..7760aea6a1 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -7,6 +7,7 @@ import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/bot_detector.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; @@ -588,6 +589,7 @@ class FakeFlutterProjectFactory implements FlutterProjectFactory { } class FakeAndroidSdk extends Fake implements AndroidSdk { + @override late bool platformToolsAvailable; @@ -602,3 +604,41 @@ class FakeAndroidStudio extends Fake implements AndroidStudio { @override String get javaPath => 'java'; } + +class FakeJava extends Fake implements Java { + FakeJava({ + this.javaHome = '/android-studio/jbr', + String binary = '/android-studio/jbr/bin/java', + JavaVersion? version, + bool canRun = true, + }): binaryPath = binary, + version = version ?? JavaVersion( + longText: 'openjdk 19.0.2 2023-01-17', + number: '19.0.2', + ), + _environment = { + if (javaHome != null) 'JAVA_HOME': javaHome, + 'PATH': '/android-studio/jbr/bin', + }, + _canRun = canRun; + + @override + String? javaHome; + + @override + String binaryPath; + + final Map _environment; + final bool _canRun; + + @override + Map get environment => _environment; + + @override + JavaVersion? version; + + @override + bool canRun() { + return _canRun; + } +}