diff --git a/org.eclipse.jdt.ls.core/plugin.xml b/org.eclipse.jdt.ls.core/plugin.xml index bfb8e7df09..e4811c4e20 100644 --- a/org.eclipse.jdt.ls.core/plugin.xml +++ b/org.eclipse.jdt.ls.core/plugin.xml @@ -100,6 +100,9 @@ + + - + diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java index a57b86893c..a525c97869 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java @@ -19,18 +19,23 @@ public enum EventType { * classpath updated event. */ ClasspathUpdated(100), - + /** * projects imported event. */ - ProjectsImported(200); - + ProjectsImported(200), + + /** + * Incompatible issue between Gradle and Jdk event. + */ + IncompatibleGradleJdkIssue(300); + private final int value; - + EventType(int value) { this.value = value; } - + public int getValue() { return value; } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java index 51be11c4cb..3cfd8ffaef 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.ClasspathOptions; import org.eclipse.jdt.ls.core.internal.handlers.FormatterHandler; import org.eclipse.jdt.ls.core.internal.handlers.ResolveSourceMappingHandler; +import org.eclipse.jdt.ls.core.internal.managers.GradleProjectImporter; import org.eclipse.jdt.ls.core.internal.commands.SourceAttachmentCommand; import org.eclipse.jdt.ls.core.internal.commands.TypeHierarchyCommand; import org.eclipse.lsp4j.ResolveTypeHierarchyItemParams; @@ -114,6 +115,9 @@ public Object executeCommand(String commandId, List arguments, IProgress params.setPosition(textParams.getPosition()); TypeHierarchyItem typeHierarchyItem = typeHierarchyCommand.typeHierarchy(params, monitor); return typeHierarchyItem; + case "java.project.upgradeGradle": + String projectUri = (String) arguments.get(0); + return GradleProjectImporter.upgradeGradleVersion(projectUri, monitor); default: break; } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java index a6d3bbdd99..df11516f61 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java @@ -39,6 +39,7 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.URIUtil; @@ -360,4 +361,13 @@ public static IPath getLongestCommonPath(IPath[] paths) { return paths[0].uptoSegment(common); } + /** + * Creates a simple error marker with the given id and status message to the given resource. + */ + public static void createErrorMarker(IResource resource, IStatus status, String id) throws CoreException { + IMarker marker = resource.createMarker(id); + marker.setAttribute(IMarker.LINE_NUMBER, 1); + marker.setAttribute(IMarker.MESSAGE, status.getMessage()); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleCompatibilityChecker.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleCompatibilityChecker.java new file mode 100644 index 0000000000..58b47b6be6 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleCompatibilityChecker.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2018-2022 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 org.eclipse.buildship.core.internal.util.gradle.GradleVersion; +import org.eclipse.jdt.core.JavaCore; + +public class GradleCompatibilityChecker { + + public static String MAX_SUPPORTED_JAVA = JavaCore.VERSION_17; + public static String CURRENT_GRADLE = "7.3.1"; + + public static boolean isIncompatible(GradleVersion gradleVersion, String javaVersion) { + if (gradleVersion == null || javaVersion == null || javaVersion.isEmpty()) { + return false; + } + String highestSupportedJava = getHighestSupportedJava(gradleVersion); + return JavaCore.compareJavaVersions(javaVersion, highestSupportedJava) > 0; + } + + 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) { + return JavaCore.VERSION_17; + } else if (baseVersion.compareTo(GradleVersion.version("7.0")) >= 0) { + return JavaCore.VERSION_16; + } else if (baseVersion.compareTo(GradleVersion.version("6.7")) >= 0) { + return JavaCore.VERSION_15; + } else if (baseVersion.compareTo(GradleVersion.version("6.3")) >= 0) { + return JavaCore.VERSION_14; + } else if (baseVersion.compareTo(GradleVersion.version("6.0")) >= 0) { + return JavaCore.VERSION_13; + } else if (baseVersion.compareTo(GradleVersion.version("5.4")) >= 0) { + return JavaCore.VERSION_12; + } else if (baseVersion.compareTo(GradleVersion.version("5.0")) >= 0) { + return JavaCore.VERSION_11; + } else if (baseVersion.compareTo(GradleVersion.version("4.7")) >= 0) { + return JavaCore.VERSION_10; + } 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; + } + } + +} 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 f264fdb9cd..fdce151eeb 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 @@ -15,9 +15,15 @@ import static java.util.Arrays.asList; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FilenameFilter; +import java.io.IOException; +import java.io.Serializable; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -29,6 +35,7 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.buildship.core.BuildConfiguration; +import org.eclipse.buildship.core.GradleBuild; import org.eclipse.buildship.core.GradleCore; import org.eclipse.buildship.core.GradleDistribution; import org.eclipse.buildship.core.SynchronizationResult; @@ -37,17 +44,28 @@ import org.eclipse.buildship.core.internal.preferences.PersistentModel; import org.eclipse.buildship.core.internal.util.gradle.GradleVersion; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.URIUtil; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.internal.launching.StandardVMType; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.ls.core.internal.AbstractProjectImporter; +import org.eclipse.jdt.ls.core.internal.EventNotification; +import org.eclipse.jdt.ls.core.internal.EventType; +import org.eclipse.jdt.ls.core.internal.IConstants; +import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; +import org.eclipse.jdt.ls.core.internal.ResourceUtils; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.jdt.ls.internal.gradle.checksums.ValidationResult; @@ -55,6 +73,8 @@ import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.MessageType; +import org.gradle.tooling.model.build.BuildEnvironment; +import org.gradle.tooling.model.build.GradleEnvironment; /** * @author Fred Bricon @@ -76,6 +96,8 @@ public class GradleProjectImporter extends AbstractProjectImporter { public static final String IMPORTING_GRADLE_PROJECTS = "Importing Gradle project(s)"; + public static final String COMPATIBILITY_MARKER_ID = IConstants.PLUGIN_ID + ".gradlecompatibilityerrormarker"; + //@formatter:off public static final String GRADLE_WRAPPER_CHEKSUM_WARNING_TEMPLATE = "Security Warning! The gradle wrapper '@wrapper@' could be malicious. " @@ -164,7 +186,13 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException { subMonitor.setTaskName(IMPORTING_GRADLE_PROJECTS); JavaLanguageServerPlugin.logInfo(IMPORTING_GRADLE_PROJECTS); subMonitor.worked(1); - directories.forEach(d -> importDir(d, subMonitor.newChild(1))); + MultiStatus compatibilityStatus = new MultiStatus(IConstants.PLUGIN_ID, -1, "Compatibility issue occurs when importing Gradle projects", null); + for (Path directory : directories) { + IStatus importStatus = importDir(directory, subMonitor.newChild(1)); + if (isFailedStatus(importStatus) && importStatus instanceof GradleCompatibilityStatus) { + compatibilityStatus.add(importStatus); + } + } // store the digest for the imported gradle projects. ProjectUtils.getGradleProjects().forEach(project -> { File buildFile = project.getFile(BUILD_GRADLE_DESCRIPTOR).getLocation().toFile(); @@ -186,14 +214,30 @@ public void importToWorkspace(IProgressMonitor monitor) throws CoreException { JavaLanguageServerPlugin.logException("Failed to update digest for gradle build file", e); } }); + for (IProject gradleProject : ProjectUtils.getGradleProjects()) { + gradleProject.deleteMarkers(COMPATIBILITY_MARKER_ID, true, IResource.DEPTH_ZERO); + } + for (IStatus status : compatibilityStatus.getChildren()) { + // only report first compatibility issue + GradleCompatibilityStatus gradleStatus = ((GradleCompatibilityStatus) status); + for (IProject gradleProject : ProjectUtils.getGradleProjects()) { + if (URIUtil.sameURI(URI.create(JDTUtils.getFileURI(gradleProject)), URI.create(gradleStatus.getProjectUri()))) { + ResourceUtils.createErrorMarker(gradleProject, gradleStatus, COMPATIBILITY_MARKER_ID); + } + } + GradleCompatibilityInfo info = new GradleCompatibilityInfo(gradleStatus.getProjectUri(), gradleStatus.getMessage(), gradleStatus.getHighestJavaVersion(), GradleCompatibilityChecker.CURRENT_GRADLE); + EventNotification notification = new EventNotification().withType(EventType.IncompatibleGradleJdkIssue).withData(info); + JavaLanguageServerPlugin.getProjectsManager().getConnection().sendEventNotification(notification); + break; + } subMonitor.done(); } - private void importDir(Path projectFolder, IProgressMonitor monitor) { + private IStatus importDir(Path projectFolder, IProgressMonitor monitor) { if (monitor.isCanceled()) { - return; + return Status.CANCEL_STATUS; } - startSynchronization(projectFolder, monitor); + return startSynchronization(projectFolder, monitor); } @@ -299,31 +343,46 @@ public static File getGradleJavaHomeFile() { return null; } - protected void startSynchronization(Path projectFolder, IProgressMonitor monitor) { + protected IStatus startSynchronization(Path projectFolder, IProgressMonitor monitor) { File location = projectFolder.toFile(); boolean shouldSynchronize = shouldSynchronize(location); if (shouldSynchronize) { BuildConfiguration build = getBuildConfiguration(projectFolder); - SynchronizationResult result = GradleCore.getWorkspace().createBuild(build).synchronize(monitor); - if (!result.getStatus().isOK()) { - JavaLanguageServerPlugin.log(result.getStatus()); + GradleBuild gradleBuild = GradleCore.getWorkspace().createBuild(build); + SynchronizationResult result = gradleBuild.synchronize(monitor); + IStatus resultStatus = result.getStatus(); + if (isFailedStatus(resultStatus)) { + try { + BuildEnvironment environment = gradleBuild.withConnection(connection -> connection.getModel(BuildEnvironment.class), monitor); + GradleEnvironment gradleEnvironment = environment.getGradle(); + String gradleVersion = gradleEnvironment.getGradleVersion(); + File javaHome = getJavaHome(getPreferences()); + String javaVersion; + if (javaHome == null) { + javaVersion = System.getProperty("java.version"); + } else { + StandardVMType type = new StandardVMType(); + javaVersion = type.readReleaseVersion(javaHome); + } + if (GradleCompatibilityChecker.isIncompatible(GradleVersion.version(gradleVersion), javaVersion)) { + Path projectName = projectFolder.getName(projectFolder.getNameCount() - 1); + String message = String.format("Can't use Java %s and Gradle %s to import Gradle project %s.", javaVersion, gradleVersion, projectName.toString()); + String highestJavaVersion = GradleCompatibilityChecker.getHighestSupportedJava(GradleVersion.version(gradleVersion)); + return new GradleCompatibilityStatus(resultStatus, message, projectFolder.toUri().toString(), highestJavaVersion); + } + } catch (Exception e) { + // Do nothing + } } + return resultStatus; } + return Status.OK_STATUS; } public static BuildConfiguration getBuildConfiguration(Path rootFolder) { GradleDistribution distribution = getGradleDistribution(rootFolder); - File javaHome = getGradleJavaHomeFile(); Preferences preferences = getPreferences(); - if (javaHome == null) { - IVMInstall javaDefaultRuntime = JavaRuntime.getDefaultVMInstall(); - if (javaDefaultRuntime != null && javaDefaultRuntime.getVMRunner(ILaunchManager.RUN_MODE) != null) { - javaHome = javaDefaultRuntime.getInstallLocation(); - } else { - String javaHomeStr = preferences.getJavaHome(); - javaHome = javaHomeStr == null ? null : new File(javaHomeStr); - } - } + File javaHome = getJavaHome(preferences); File gradleUserHome = getGradleUserHomeFile(); List gradleArguments = preferences.getGradleArguments(); List gradleJvmArguments = preferences.getGradleJvmArguments(); @@ -344,6 +403,20 @@ public static BuildConfiguration getBuildConfiguration(Path rootFolder) { return build; } + private static File getJavaHome(Preferences preferences) { + File javaHome = getGradleJavaHomeFile(); + if (javaHome == null) { + IVMInstall javaDefaultRuntime = JavaRuntime.getDefaultVMInstall(); + if (javaDefaultRuntime != null && javaDefaultRuntime.getVMRunner(ILaunchManager.RUN_MODE) != null) { + javaHome = javaDefaultRuntime.getInstallLocation(); + } else { + String javaHomeStr = preferences.getJavaHome(); + javaHome = javaHomeStr == null ? null : new File(javaHomeStr); + } + } + return javaHome; + } + public static boolean shouldSynchronize(File location) { for (IProject project : ProjectUtils.getGradleProjects()) { File projectDir = project.getLocation() == null ? null : project.getLocation().toFile(); @@ -387,8 +460,83 @@ public boolean accept(File dir, String name) { return shouldSynchronize; } + public static boolean upgradeGradleVersion(String projectUri, IProgressMonitor monitor) { + String newDistributionUrl = String.format("https://services.gradle.org/distributions/gradle-%s-bin.zip", GradleCompatibilityChecker.CURRENT_GRADLE); + Path projectFolder = Paths.get(URI.create(projectUri)); + File propertiesFile = projectFolder.resolve("gradle").resolve("wrapper").resolve("gradle-wrapper.properties").toFile(); + Properties properties = new Properties(); + if (propertiesFile.exists()) { + try (FileInputStream stream = new FileInputStream(propertiesFile)) { + properties.load(stream); + properties.setProperty("distributionUrl", newDistributionUrl); + } catch (IOException e) { + return false; + } + } else { + properties.setProperty("distributionBase", "GRADLE_USER_HOME"); + properties.setProperty("distributionPath", "wrapper/dists"); + properties.setProperty("distributionUrl", newDistributionUrl); + properties.setProperty("zipStoreBase", "GRADLE_USER_HOME"); + properties.setProperty("zipStorePath", "wrapper/dists"); + } + try { + properties.store(new FileOutputStream(propertiesFile), null); + } catch (Exception e) { + return false; + } + BuildConfiguration build = getBuildConfiguration(projectFolder); + GradleBuild gradleBuild = GradleCore.getWorkspace().createBuild(build); + try { + gradleBuild.withConnection(connection -> { + connection.newBuild().forTasks("wrapper").run(); + return null; + }, monitor); + } catch (Exception e) { + return false; + } + return true; + } + @Override public void reset() { } + private static boolean isFailedStatus(IStatus status) { + return status != null && !status.isOK() && status.getException() != null; + } + + public class GradleCompatibilityStatus extends Status { + + private String projectUri; + private String highestJavaVersion; + + public GradleCompatibilityStatus(IStatus status, String message, String projectUri, String highestJavaVersion) { + super(status.getSeverity(), status.getPlugin(), status.getCode(), message, status.getException()); + this.projectUri = projectUri; + this.highestJavaVersion = highestJavaVersion; + } + + public String getProjectUri() { + return this.projectUri; + } + + public String getHighestJavaVersion() { + return this.highestJavaVersion; + } + } + + private class GradleCompatibilityInfo implements Serializable { + + private String projectUri; + private String message; + private String highestJavaVersion; + private String recommendedGradleVersion; + + public GradleCompatibilityInfo(String projectPath, String message, String highestJavaVersion, String recommendedGradleVersion) { + this.projectUri = projectPath; + this.message = message; + this.highestJavaVersion = highestJavaVersion; + this.recommendedGradleVersion = recommendedGradleVersion; + } + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java index 8b0f74cf2c..4cfad3a754 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java @@ -17,16 +17,13 @@ import java.io.File; import java.net.URI; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream;