From 792c3432c53ec2aa15ecc46fea1b937e0a7b9866 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Mon, 28 Aug 2023 13:56:11 +0800 Subject: [PATCH] Select suitable JDK to launch Gradle. (#2812) - 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..ab9cff8f8f 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 @@ -204,6 +204,8 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException { subMonitor.setTaskName(IMPORTING_GRADLE_PROJECTS); JavaLanguageServerPlugin.logInfo(IMPORTING_GRADLE_PROJECTS); subMonitor.worked(1); + // run just once at the first project, assuming that all projects are using the same gradle version. + inferGradleJavaHome(directories.iterator().next(), monitor); MultiStatus compatibilityStatus = new MultiStatus(IConstants.PLUGIN_ID, -1, "Compatibility issue occurs when importing Gradle projects", null); MultiStatus gradleUpgradeWrapperStatus = new MultiStatus(IConstants.PLUGIN_ID, -1, "Gradle upgrade wrapper", null); for (Path directory : directories) { @@ -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")); + } +}