From 4fabed3b66ac82b24a711c0321aec27066ff2511 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Thu, 24 Aug 2023 16:41:43 +0800 Subject: [PATCH] Select suitable JDK to launch Gradle. - When the Gradle Java home preference is not set, and the default JDK is not compatible with the project Gradle, try to find compatible JDK if there is one available. Signed-off-by: Sheng Chen --- .../managers/GradleProjectImporter.java | 36 ++++++ .../core/internal/managers/GradleUtils.java | 105 ++++++++++++++++-- .../managers/GradleProjectImporterTest.java | 8 ++ .../internal/managers/GradleUtilsTest.java | 34 ++++++ 4 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtilsTest.java diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporter.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporter.java index 613e9ec0c2..46793a3962 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporter.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporter.java @@ -199,6 +199,8 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException { if (!applies(monitor)) { return; } + // run just once at the first project, assuming that all projects are using the same gradle version. + inferGradleJavaHome(directories.iterator().next(), monitor); int projectSize = directories.size(); SubMonitor subMonitor = SubMonitor.convert(monitor, projectSize + 1); subMonitor.setTaskName(IMPORTING_GRADLE_PROJECTS); @@ -291,6 +293,33 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException { subMonitor.done(); } + private void inferGradleJavaHome(Path projectFolder, IProgressMonitor monitor) { + if (StringUtils.isNotBlank(getPreferences().getGradleJavaHome())) { + return; + } + + File javaHome = getJavaHome(getPreferences()); + String javaVersion; + if (javaHome == null) { + javaVersion = System.getProperty("java.version"); + } else { + StandardVMType type = new StandardVMType(); + javaVersion = type.readReleaseVersion(javaHome); + } + if (StringUtils.isBlank(javaVersion)) { + // return if failed to get java version. + return; + } + GradleVersion gradleVersion = GradleUtils.getGradleVersion(projectFolder, monitor); + if (GradleUtils.isIncompatible(gradleVersion, javaVersion)) { + String highestJavaVersion = GradleUtils.getHighestSupportedJava(gradleVersion); + File javaHomeFile = GradleUtils.getJdkToLaunchDaemon(highestJavaVersion); + if (javaHomeFile != null) { + getPreferences().setGradleJavaHome(javaHomeFile.getAbsolutePath()); + } + } + } + private IStatus importDir(Path projectFolder, IProgressMonitor monitor) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; @@ -438,11 +467,18 @@ protected IStatus startSynchronization(Path projectFolder, IProgressMonitor moni } public static BuildConfiguration getBuildConfiguration(Path rootFolder) { + return getBuildConfiguration(rootFolder, false); + } + + public static BuildConfiguration getBuildConfiguration(Path rootFolder, boolean noDaemon) { GradleDistribution distribution = getGradleDistribution(rootFolder); Preferences preferences = getPreferences(); File javaHome = getJavaHome(preferences); File gradleUserHome = getGradleUserHomeFile(); List gradleArguments = new ArrayList<>(); + if (noDaemon) { + gradleArguments.add("--no-daemon"); + } gradleArguments.addAll(getGradleInitScriptArgs()); gradleArguments.addAll(preferences.getGradleArguments()); List gradleJvmArguments = preferences.getGradleJvmArguments(); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtils.java index c4365d7e66..2acd5e95f3 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtils.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; +import java.lang.Runtime.Version; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -28,8 +29,11 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.regex.Pattern; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; import org.eclipse.buildship.core.BuildConfiguration; import org.eclipse.buildship.core.GradleBuild; import org.eclipse.buildship.core.GradleCore; @@ -39,14 +43,21 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.launching.StandardVMType; +import org.eclipse.jdt.launching.AbstractVMInstall; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstallType; +import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; +import org.eclipse.jdt.ls.core.internal.RuntimeEnvironment; +import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.gradle.tooling.model.build.BuildEnvironment; import org.gradle.tooling.model.build.GradleEnvironment; public class GradleUtils { - public static String MAX_SUPPORTED_JAVA = JavaCore.VERSION_17; // see https://github.com/gradle/gradle/pull/17397 public static String INVALID_TYPE_FIXED_VERSION = "7.2"; // see https://github.com/gradle/gradle/issues/890 @@ -55,11 +66,6 @@ public class GradleUtils { private static final String MESSAGE_DIGEST_ALGORITHM = "SHA-256"; - /** - * A pattern to parse annotation processing arguments. - */ - private static final Pattern OPTION_PATTERN = Pattern.compile("-A([^ \\t\"']+)"); - public static boolean isIncompatible(GradleVersion gradleVersion, String javaVersion) { if (gradleVersion == null || javaVersion == null || javaVersion.isEmpty()) { return false; @@ -72,7 +78,13 @@ public static String getHighestSupportedJava(GradleVersion gradleVersion) { GradleVersion baseVersion = gradleVersion.getBaseVersion(); try { // https://docs.gradle.org/current/userguide/compatibility.html - if (baseVersion.compareTo(GradleVersion.version("7.3")) >= 0) { + if (baseVersion.compareTo(GradleVersion.version("8.3")) >= 0) { + return JavaCore.VERSION_20; + } else if (baseVersion.compareTo(GradleVersion.version("7.6")) >= 0) { + return JavaCore.VERSION_19; + } else if (baseVersion.compareTo(GradleVersion.version("7.5")) >= 0) { + return JavaCore.VERSION_18; + } else if (baseVersion.compareTo(GradleVersion.version("7.3")) >= 0) { return JavaCore.VERSION_17; } else if (baseVersion.compareTo(GradleVersion.version("7.0")) >= 0) { return JavaCore.VERSION_16; @@ -91,10 +103,10 @@ public static String getHighestSupportedJava(GradleVersion gradleVersion) { } else if (baseVersion.compareTo(GradleVersion.version("4.3")) >= 0) { return JavaCore.VERSION_9; } - return JavaCore.VERSION_1_8; } catch (IllegalArgumentException e) { - return MAX_SUPPORTED_JAVA; + // ignore } + return JavaCore.VERSION_1_8; } public static boolean hasGradleInvalidTypeCodeException(IStatus status, Path projectFolder, IProgressMonitor monitor) { @@ -123,7 +135,7 @@ public static boolean isGradleInvalidTypeCodeException(Throwable throwable) { public static GradleVersion getGradleVersion(Path projectFolder, IProgressMonitor monitor) { try { - BuildConfiguration build = GradleProjectImporter.getBuildConfiguration(projectFolder); + BuildConfiguration build = GradleProjectImporter.getBuildConfiguration(projectFolder, true); GradleBuild gradleBuild = GradleCore.getWorkspace().createBuild(build); BuildEnvironment environment = gradleBuild.withConnection(connection -> connection.getModel(BuildEnvironment.class), monitor); GradleEnvironment gradleEnvironment = environment.getGradle(); @@ -293,4 +305,75 @@ public static void synchronizeAnnotationProcessingConfiguration(IProgressMonitor } } } + + /** + * Find the latest JDK but equal or lower than the {@code highestJavaVersion}. + */ + public static File getJdkToLaunchDaemon(String highestJavaVersion) { + if (StringUtils.isBlank(highestJavaVersion)) { + return null; + } + + Map jdks = getAllVmInstalls();; + Entry selected = null; + for (Entry jdk : jdks.entrySet()) { + String javaVersion = jdk.getKey(); + if (Version.parse(javaVersion).compareTo(Version.parse(highestJavaVersion)) <= 0 + && (selected == null || Version.parse(selected.getKey()).compareTo(Version.parse(javaVersion)) < 0)) { + selected = jdk; + } + } + + return selected == null ? null : selected.getValue(); + } + + /** + * Get all the available JDK installations in the Eclipse VM registry. If multiple installations + * are found for the same major version, the first found one is return. + * + * The results are returned as map, where key is the major version and value is the file instance of + * the installation. + */ + private static Map getAllVmInstalls() { + List vmList = Stream.of(JavaRuntime.getVMInstallTypes()) + .map(IVMInstallType::getVMInstalls) + .flatMap(Arrays::stream) + .toList(); + Map vmInstalls = new HashMap<>(); + for (IVMInstall vmInstall : vmList) { + if (vmInstall instanceof AbstractVMInstall vm) { + String javaVersion = getMajorJavaVersion(vm.getJavaVersion()); + if (StringUtils.isBlank(javaVersion) || vm.getInstallLocation() == null) { + continue; + } + + vmInstalls.putIfAbsent(javaVersion, vm.getInstallLocation()); + } + } + + Preferences preferences = JavaLanguageServerPlugin.getPreferencesManager().getPreferences(); + Set runtimes = preferences.getRuntimes(); + for (RuntimeEnvironment runtime : runtimes) { + if (StringUtils.isBlank(runtime.getPath())) { + continue; + } + File javaHome = new File(runtime.getPath()); + if (vmInstalls.containsValue(javaHome)) { + continue; + } + + String javaVersion = new StandardVMType().readReleaseVersion(javaHome); + if (StringUtils.isNotBlank(javaVersion)) { + // the user preference should have higher priority and replace + // the existing one if the major version is the same. + vmInstalls.put(getMajorJavaVersion(javaVersion), javaHome); + } + } + + return vmInstalls; + } + + public static String getMajorJavaVersion(String version) { + return CompilerOptions.versionFromJdkLevel(CompilerOptions.versionToJdkLevel(version)); + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporterTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporterTest.java index 5b07165db7..6020cead29 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporterTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleProjectImporterTest.java @@ -65,6 +65,7 @@ import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.jdt.ls.core.internal.preferences.Preferences.FeatureStatus; import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -79,6 +80,12 @@ public class GradleProjectImporterTest extends AbstractGradleBasedTest{ private static final String GRADLE1_PATTERN = "**/gradle1"; + private String gradleJavaHome; + + @Before + public void setUp() { + gradleJavaHome = JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getGradleJavaHome(); + } @Test public void importSimpleGradleProject() throws Exception { @@ -94,6 +101,7 @@ public void importSimpleGradleProject() throws Exception { public void cleanUp() throws Exception { super.cleanUp(); Job.getJobManager().join(CorePlugin.GRADLE_JOB_FAMILY, new NullProgressMonitor()); + JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setGradleJavaHome(gradleJavaHome); } @Test diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtilsTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtilsTest.java new file mode 100644 index 0000000000..c00f52fa6e --- /dev/null +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/GradleUtilsTest.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2023 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.ls.core.internal.managers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Test; + +public class GradleUtilsTest { + @Test + public void testGetJdkToLaunchDaemon() { + assertEquals("17", GradleUtils.getMajorJavaVersion("17.0.8")); + assertEquals("1.8", GradleUtils.getMajorJavaVersion("1.8.0_202")); + } + + @Test + public void testGetMajorJavaVersion() { + File javaHome = GradleUtils.getJdkToLaunchDaemon("10"); + assertTrue(javaHome.getAbsolutePath().contains("fakejdk" + File.separator + "10")); + } +}