mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This PR attempts to improve clarity of androids section of `flutter doctor -v` output by providing explicit information about which JDK is being used and how to configure a different one if needed. ### Before ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java ``` ### After 1. When JDK is from Android Studio: ```console • Java binary at: /Users/users/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This is the JDK bundled with latest Android Studio installation To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` 2. When JDK is from JAVA_HOME env variable: ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK is specified by JAVA_HOME environment variable To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` 3. When path to JDK is set in flutter config: ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK was found in system PATH To change current JDK, run: `flutter config --jdk-dir="path/to/jdk"` ``` 4. When java binary is found in PATH (as fallback) ```console • Java binary at: /Users/user/Applications/Android Studio Ladybug Feature Drop 2024.2.2 Canary 2.app/Contents/jbr/Contents/Home/bin/java This JDK is specified in Flutter configuration To manually set a custom JDK path, use: `flutter config --jdk-dir="path/to/jdk"` ``` ### Motivation I think it's described in https://github.com/flutter/flutter/issues/153156#issuecomment-2336814991. TLDR; many developers struggle with Java-related issues and more verbose doctor's output will (presumably) improve DX in that part. fixes #153156 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
368 lines
13 KiB
Dart
368 lines
13 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package: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/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/version.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 Config config;
|
|
late Logger logger;
|
|
late FileSystem fs;
|
|
late Platform platform;
|
|
late FakeProcessManager processManager;
|
|
|
|
setUp(() {
|
|
config = Config.test();
|
|
logger = BufferLogger.test();
|
|
fs = MemoryFileSystem.test();
|
|
platform = FakePlatform(environment: <String, String>{
|
|
'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');
|
|
const JavaSource expectedJavaHomeSource = JavaSource.androidStudio;
|
|
|
|
processManager.addCommand(FakeCommand(
|
|
command: <String>[
|
|
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(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
)!;
|
|
|
|
expect(java.javaHome, androidStudioBundledJdkHome);
|
|
expect(java.binaryPath, expectedJavaBinaryPath);
|
|
|
|
expect(java.version!.toString(), 'OpenJDK Runtime Environment Zulu19.32+15-CA (build 19.0.2+7)');
|
|
expect(java.version, equals(Version(19, 0, 2)));
|
|
expect(java.javaSource, expectedJavaHomeSource);
|
|
});
|
|
|
|
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');
|
|
const JavaSource expectedJavaHomeSource = JavaSource.javaHome;
|
|
|
|
final Java java = Java.find(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: FakePlatform(environment: <String, String>{
|
|
Java.javaHomeEnvironmentVariable: javaHome,
|
|
}),
|
|
processManager: processManager,
|
|
)!;
|
|
|
|
expect(java.javaHome, javaHome);
|
|
expect(java.binaryPath, expectedJavaBinaryPath);
|
|
expect(java.javaSource, expectedJavaHomeSource);
|
|
});
|
|
|
|
testWithoutContext('returns the java binary found on PATH if no other can be found', () {
|
|
final AndroidStudio androidStudio = _FakeAndroidStudioWithoutJdk();
|
|
final OperatingSystemUtils os = _FakeOperatingSystemUtilsWithJava(fileSystem);
|
|
const JavaSource expectedJavaHomeSource = JavaSource.path;
|
|
|
|
|
|
processManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>['which', 'java'],
|
|
stdout: '/fake/which/java/path',
|
|
),
|
|
);
|
|
|
|
final Java java = Java.find(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
)!;
|
|
|
|
expect(java.javaHome, isNull);
|
|
expect(java.binaryPath, os.which('java')!.path);
|
|
expect(java.javaSource, expectedJavaHomeSource);
|
|
});
|
|
|
|
testWithoutContext('returns null if no java could be found', () {
|
|
final AndroidStudio androidStudio = _FakeAndroidStudioWithoutJdk();
|
|
processManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>['which', 'java'],
|
|
exitCode: 1,
|
|
),
|
|
);
|
|
final Java? java = Java.find(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: platform,
|
|
processManager: processManager,
|
|
);
|
|
expect(java, isNull);
|
|
});
|
|
|
|
testWithoutContext('finds and prefers JDK found at config item "jdk-dir" if it is set', () {
|
|
const String configuredJdkPath = '/jdk';
|
|
config.setValue('jdk-dir', configuredJdkPath);
|
|
JavaSource expectedJavaHomeSource = JavaSource.flutterConfig;
|
|
|
|
processManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>['which', 'java'],
|
|
stdout: '/fake/which/java/path',
|
|
),
|
|
);
|
|
|
|
final _FakeAndroidStudioWithJdk androidStudio = _FakeAndroidStudioWithJdk();
|
|
final FakePlatform platformWithJavaHome = FakePlatform(
|
|
environment: <String, String>{
|
|
'JAVA_HOME': '/old/jdk'
|
|
},
|
|
);
|
|
Java? java = Java.find(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: platformWithJavaHome,
|
|
processManager: processManager,
|
|
);
|
|
|
|
expect(java, isNotNull);
|
|
expect(java!.javaHome, configuredJdkPath);
|
|
expect(java.binaryPath, fs.path.join(configuredJdkPath, 'bin', 'java'));
|
|
expect(java.javaSource, expectedJavaHomeSource);
|
|
|
|
config.removeValue('jdk-dir');
|
|
|
|
expectedJavaHomeSource = JavaSource.androidStudio;
|
|
|
|
java = Java.find(
|
|
config: config,
|
|
androidStudio: androidStudio,
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
platform: platformWithJavaHome,
|
|
processManager: processManager,
|
|
);
|
|
|
|
expect(java, isNotNull);
|
|
assert(androidStudio.javaPath != configuredJdkPath);
|
|
expect(java!.javaHome, androidStudio.javaPath);
|
|
expect(java.binaryPath, fs.path.join(androidStudio.javaPath!, 'bin', 'java'));
|
|
expect(java.javaSource, expectedJavaHomeSource);
|
|
});
|
|
});
|
|
|
|
group('version', () {
|
|
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',
|
|
javaSource: JavaSource.javaHome,
|
|
);
|
|
});
|
|
|
|
void addJavaVersionCommand(String output) {
|
|
processManager.addCommand(
|
|
FakeCommand(
|
|
command: <String>[java.binaryPath, '--version'],
|
|
stdout: output,
|
|
),
|
|
);
|
|
}
|
|
|
|
testWithoutContext('is null when java binary cannot be run', () async {
|
|
addJavaVersionCommand('');
|
|
processManager.excludedExecutables.add('java');
|
|
|
|
expect(java.version, null);
|
|
});
|
|
|
|
testWithoutContext('is null when java --version returns a non-zero exit code', () async {
|
|
processManager.addCommand(
|
|
FakeCommand(
|
|
command: <String>[java.binaryPath, '--version'],
|
|
exitCode: 1,
|
|
),
|
|
);
|
|
expect(java.version, null);
|
|
});
|
|
|
|
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 Version version = java.version!;
|
|
expect(version.toString(), 'Java(TM) SE Runtime Environment (build 1.8.0_202-b10)');
|
|
expect(version, equals(Version(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 Version version = java.version!;
|
|
expect(version.toString(), 'Java(TM) SE Runtime Environment (build 11.0.14+10-b13)');
|
|
expect(version, equals(Version(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 Version version = java.version!;
|
|
expect(version.toString(), 'OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)');
|
|
expect(version, equals(Version(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 Version version = java.version!;
|
|
expect(version.toString(), 'OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)');
|
|
expect(version, equals(Version(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 Version version = java.version!;
|
|
expect(version.toString(), 'OpenJDK Runtime Environment Homebrew (build 19.0.2)');
|
|
expect(version, equals(Version(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 Version version = java.version!;
|
|
expect(version.toString(), 'OpenJDK Runtime Environment 18.9 (build 11.0.2+9)');
|
|
expect(version, equals(Version(11, 0, 2)));
|
|
});
|
|
|
|
testWithoutContext('parses jdk two number versions', () {
|
|
addJavaVersionCommand('openjdk 19.0 2023-01-17');
|
|
final Version version = java.version!;
|
|
expect(version.toString(), 'openjdk 19.0 2023-01-17');
|
|
expect(version, equals(Version(19, 0, null)));
|
|
});
|
|
|
|
testWithoutContext('parses jdk 21 with patch numbers', () {
|
|
addJavaVersionCommand('''
|
|
java 21.0.1 2023-09-19 LTS
|
|
Java(TM) SE Runtime Environment (build 21+35-LTS-2513)
|
|
Java HotSpot(TM) 64-Bit Server VM (build 21+35-LTS-2513, mixed mode, sharing)
|
|
''');
|
|
final Version? version = java.version;
|
|
expect(version, equals(Version(21, 0, 1)));
|
|
});
|
|
|
|
testWithoutContext('parses jdk 21 with no patch numbers', () {
|
|
addJavaVersionCommand('''
|
|
java 21 2023-09-19 LTS
|
|
Java(TM) SE Runtime Environment (build 21+35-LTS-2513)
|
|
Java HotSpot(TM) 64-Bit Server VM (build 21+35-LTS-2513, mixed mode, sharing)
|
|
''');
|
|
final Version? version = java.version;
|
|
expect(version, equals(Version(21, 0, 0)));
|
|
});
|
|
testWithoutContext('parses openjdk 21 with no patch numbers', () {
|
|
addJavaVersionCommand('''
|
|
openjdk version "21" 2023-09-19
|
|
OpenJDK Runtime Environment (build 21+35)
|
|
OpenJDK 64-Bit Server VM (build 21+35, mixed mode, sharing)
|
|
''');
|
|
final Version? version = java.version;
|
|
expect(version, equals(Version(21, 0, 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);
|
|
}
|
|
}
|