From b0864b140e8ee9e85d5cfd5ca15b0c33cc987226 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Fri, 8 May 2026 15:13:42 +0800 Subject: [PATCH 01/21] Fix Scala Gradle classpath pollution Limit Scala Gradle classpath augmentation to projects that actually apply the Scala plugin and ignore artifacts produced by Gradle project dependencies. This prevents local project dependencies from being written as raw build/libs library entries. Stop emitting source/resource markers from the Scala Gradle init script and remove the Java-side resource exclusion rewrite so src/main/resources remains visible on the classpath. Add a multi-project Scala Gradle fixture covering project dependencies and resources. --- .../gradle/scala/javals.gradle | 33 +++++++------------ .../internal/managers/ScalaGradleSupport.java | 32 ++---------------- .../projects/gradle/scala/app/build.gradle | 2 ++ .../src/main/resources/application.properties | 1 + .../projects/gradle/scala/common/build.gradle | 7 ++++ .../src/main/java/org/example/Library.java | 8 +++++ .../projects/gradle/scala/settings.gradle | 2 +- .../managers/GradleProjectImporterTest.java | 6 ++++ 8 files changed, 38 insertions(+), 53 deletions(-) create mode 100644 org.eclipse.jdt.ls.tests/projects/gradle/scala/app/src/main/resources/application.properties create mode 100644 org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle create mode 100644 org.eclipse.jdt.ls.tests/projects/gradle/scala/common/src/main/java/org/example/Library.java diff --git a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle index df74f297f4..2c043721e2 100644 --- a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle +++ b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle @@ -1,9 +1,11 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.jvm.JvmLibrary import org.gradle.language.base.artifact.SourcesArtifact allprojects { afterEvaluate { project -> + if (!project.plugins.hasPlugin('scala')) return if (project.tasks.findByName('javalsCheckProject')) return project.tasks.register('javalsCheckProject') { doLast { @@ -36,28 +38,15 @@ allprojects { def conf = project.configurations[name] if (conf.canBeResolved) { conf.resolvedConfiguration.resolvedArtifacts.each { artifact -> - def binPath = artifact.file.absolutePath - if (!processedFiles.contains(binPath)) { - def id = artifact.moduleVersion.id - def key = "${id.group}:${id.name}".toString() - def srcPath = sourceMap[key] ?: "NO_SOURCE" - println "LIB|${binPath}|${srcPath}" - processedFiles.add(binPath) - } - } - } - } - if (project.hasProperty('sourceSets')) { - project.sourceSets.each { ss -> - def scope = ss.name // 'main', 'test', 'integrationTest' - ss.allSource.srcDirs.each { dir -> - if (dir.exists() && !ss.resources.srcDirs.contains(dir)) { - println "SRC|${scope}|${dir.absolutePath}" - } - } - ss.resources.srcDirs.each { dir -> - if (dir.exists()) { - println "RES|${scope}|${dir.absolutePath}" + if (!(artifact.id.componentIdentifier instanceof ProjectComponentIdentifier)) { + def binPath = artifact.file.absolutePath + if (!processedFiles.contains(binPath)) { + def id = artifact.moduleVersion.id + def key = "${id.group}:${id.name}".toString() + def srcPath = sourceMap[key] ?: "NO_SOURCE" + println "LIB|${binPath}|${srcPath}" + processedFiles.add(binPath) + } } } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java index 52114ab9aa..742eb7a3f1 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java @@ -62,7 +62,6 @@ public class ScalaGradleSupport { public static final Path CONTAINER_PATH = new Path("org.eclipse.buildship.core.gradleclasspathcontainer"); - public static String[] EXCLUSIONS_PATTERNS = { "**" }; // Gradle Tooling API removes several scala libraries and adds the Scala builder and container that aren't recognized by Java LS. // See https://github.com/gradle/gradle/blob/b3c5d40e82439da4627b38b4ced93121e551b0eb/platforms/ide/ide-plugins/src/main/java/org/gradle/plugins/ide/eclipse/EclipsePlugin.java#L375-L377 @@ -169,13 +168,10 @@ private File getInitScript() throws IOException { * The process method checks if a library has been added and if not, adds it. * Gradle Tooling API excludes some scala libraries because it expects Scala IDE to add them. * Since Scala IDE for VS Code doesn't exist, we add those libraries. - * The method also checks resources folders and excludes them from the compilation. */ private static void process(IProject project, String output, IProgressMonitor monitor) { List taskClasspaths = new LinkedList<>(); Map taskClasspathSources = new HashMap<>(); - List sources = new LinkedList<>(); - List resources = new LinkedList<>(); boolean start = false; try (BufferedReader reader = new BufferedReader(new StringReader(output))) { String line; @@ -200,11 +196,9 @@ private static void process(IProject project, String output, IProgressMonitor mo break; } case "SRC": { - sources.add(elements[2]); break; } case "RES": { - resources.add(elements[2]); break; } default: @@ -232,7 +226,7 @@ private static void process(IProject project, String output, IProgressMonitor mo List toAdd = getMissingPaths(javaProject, paths); if (!toAdd.isEmpty()) { try { - configureClasspath(javaProject, toAdd, taskClasspathSources, resources, monitor); + configureClasspath(javaProject, toAdd, taskClasspathSources, monitor); } catch (JavaModelException e) { JavaLanguageServerPlugin.logException(e); } @@ -240,7 +234,7 @@ private static void process(IProject project, String output, IProgressMonitor mo } } - private static void configureClasspath(IJavaProject javaProject, List toAdd, Map taskClasspathSources, List resources, IProgressMonitor monitor) throws JavaModelException { + private static void configureClasspath(IJavaProject javaProject, List toAdd, Map taskClasspathSources, IProgressMonitor monitor) throws JavaModelException { IClasspathEntry[] classpath = javaProject.getRawClasspath(); List entries = new LinkedList<>(); for (String path : toAdd) { @@ -263,28 +257,6 @@ private static void configureClasspath(IJavaProject javaProject, List to .distinct() .toArray(IClasspathEntry[]::new); // @formatter:on - for (int i = 0; i < newClasspath.length; i++) { - IClasspathEntry entry = newClasspath[i]; - if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - Optional optional = resources.stream().filter(r -> Objects.equals(entry.getPath().removeFirstSegments(1), new Path(r).makeRelativeTo(javaProject.getProject().getLocation()))).findFirst(); - if (optional.isPresent()) { - IPath[] exclusions = entry.getExclusionPatterns(); - if (exclusions == null) { - exclusions = new IPath[0]; - } - List currentExclusions = new ArrayList<>(Arrays.asList(exclusions)); - for (String ext : EXCLUSIONS_PATTERNS) { - IPath newPath = new Path(ext); - boolean exists = currentExclusions.stream().anyMatch(existingPath -> existingPath.toString().equals(ext)); - if (!exists) { - currentExclusions.add(newPath); - } - } - IClasspathEntry newEntry = JavaCore.newSourceEntry(entry.getPath(), entry.getInclusionPatterns(), currentExclusions.toArray(new IPath[0]), entry.getOutputLocation(), entry.getExtraAttributes()); - newClasspath[i] = newEntry; - } - } - } javaProject.setRawClasspath(newClasspath, monitor); } diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/build.gradle b/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/build.gradle index f51ee8b896..44e018d44c 100644 --- a/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/build.gradle +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/build.gradle @@ -31,6 +31,8 @@ sourceSets { dependencies { + implementation project(':common') + // Use Scala 2.13 in our library project implementation libs.scala.library diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/src/main/resources/application.properties b/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/src/main/resources/application.properties new file mode 100644 index 0000000000..8baed2b84b --- /dev/null +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/app/src/main/resources/application.properties @@ -0,0 +1 @@ +sample.value=true diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle new file mode 100644 index 0000000000..fa88eeb1c2 --- /dev/null +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/src/main/java/org/example/Library.java b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/src/main/java/org/example/Library.java new file mode 100644 index 0000000000..8b95fa6005 --- /dev/null +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/src/main/java/org/example/Library.java @@ -0,0 +1,8 @@ +package org.example; + +public class Library { + + public String name() { + return "library"; + } +} diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/settings.gradle b/org.eclipse.jdt.ls.tests/projects/gradle/scala/settings.gradle index 2c6e7a8b79..942019353b 100644 --- a/org.eclipse.jdt.ls.tests/projects/gradle/scala/settings.gradle +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/settings.gradle @@ -4,4 +4,4 @@ plugins { } rootProject.name = 'scala' -include('app') +include('app', 'common') 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 9357dea772..d68d2946dd 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 @@ -868,6 +868,12 @@ public void testScalaSupportEnabled() throws Exception { IProject project = ProjectUtils.getProject("app"); assertTrue(ProjectUtils.isGradleProject(project)); assertNoErrors(project); + IClasspathEntry[] classpathEntries = JavaCore.create(project).getRawClasspath(); + boolean hasBuildLibEntry = Arrays.stream(classpathEntries).anyMatch(cpe -> cpe.getEntryKind() == IClasspathEntry.CPE_LIBRARY && cpe.getPath().toString().replace('\\', '/').contains("/build/libs/")); + boolean hasUnexcludedResources = Arrays.stream(classpathEntries).anyMatch(cpe -> cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE && cpe.getPath().toString().endsWith("src/main/resources") + && Arrays.stream(cpe.getExclusionPatterns()).noneMatch(pattern -> "**".equals(pattern.toString()))); + assertFalse(hasBuildLibEntry); + assertTrue(hasUnexcludedResources); } finally { this.preferences.setScalaSupportEnabled(oldScalaSupported); } From 683085c6f3f2bb02a36bdf607d588c7e40547c41 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Fri, 8 May 2026 15:42:37 +0800 Subject: [PATCH 02/21] Handle non-Scala Gradle projects in Scala support Always register the javalsCheckProject init-script task so Java-only Gradle projects do not fail with task-not-found when Scala support probes them. The task now no-ops unless the project applies the Scala plugin. Remove obsolete SRC and RES parser branches and make artifact source lookup tolerant of artifacts without moduleVersion metadata. Add coverage for running the Scala support init-script task against a non-Scala subproject. --- .../gradle/scala/javals.gradle | 73 ++++++++++--------- .../internal/managers/ScalaGradleSupport.java | 6 -- .../managers/GradleProjectImporterTest.java | 23 ++++++ 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle index 2c043721e2..033913e4b3 100644 --- a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle +++ b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle @@ -5,53 +5,54 @@ import org.gradle.language.base.artifact.SourcesArtifact allprojects { afterEvaluate { project -> - if (!project.plugins.hasPlugin('scala')) return if (project.tasks.findByName('javalsCheckProject')) return project.tasks.register('javalsCheckProject') { doLast { - println "JAVALS_START" - def targetConfNames = ['runtimeClasspath', 'testRuntimeClasspath', 'scalaRuntime'] - def componentIds = [] as Set - def processedFiles = [] as Set - targetConfNames.findAll { project.configurations.findByName(it) }.each { name -> - def conf = project.configurations[name] - if (conf.canBeResolved) { - conf.incoming.resolutionResult.allComponents.each { - if (it.id instanceof ModuleComponentIdentifier) componentIds.add(it.id) + if (project.plugins.hasPlugin('scala')) { + println "JAVALS_START" + def targetConfNames = ['runtimeClasspath', 'testRuntimeClasspath', 'scalaRuntime'] + def componentIds = [] as Set + def processedFiles = [] as Set + targetConfNames.findAll { project.configurations.findByName(it) }.each { name -> + def conf = project.configurations[name] + if (conf.canBeResolved) { + conf.incoming.resolutionResult.allComponents.each { + if (it.id instanceof ModuleComponentIdentifier) componentIds.add(it.id) + } } } - } - def sourceMap = [:] - if (!componentIds.isEmpty()) { - project.dependencies.createArtifactResolutionQuery() - .forComponents(componentIds) - .withArtifacts(JvmLibrary, SourcesArtifact) - .execute() - .resolvedComponents.each { res -> - def sources = res.getArtifacts(SourcesArtifact) - if (!sources.isEmpty()) { - sourceMap["${res.id.group}:${res.id.module}".toString()] = sources.iterator().next().file.absolutePath + def sourceMap = [:] + if (!componentIds.isEmpty()) { + project.dependencies.createArtifactResolutionQuery() + .forComponents(componentIds) + .withArtifacts(JvmLibrary, SourcesArtifact) + .execute() + .resolvedComponents.each { res -> + def sources = res.getArtifacts(SourcesArtifact) + if (!sources.isEmpty()) { + sourceMap["${res.id.group}:${res.id.module}".toString()] = sources.iterator().next().file.absolutePath + } } - } - } - targetConfNames.findAll { project.configurations.findByName(it) }.each { name -> - def conf = project.configurations[name] - if (conf.canBeResolved) { - conf.resolvedConfiguration.resolvedArtifacts.each { artifact -> - if (!(artifact.id.componentIdentifier instanceof ProjectComponentIdentifier)) { - def binPath = artifact.file.absolutePath - if (!processedFiles.contains(binPath)) { - def id = artifact.moduleVersion.id - def key = "${id.group}:${id.name}".toString() - def srcPath = sourceMap[key] ?: "NO_SOURCE" - println "LIB|${binPath}|${srcPath}" - processedFiles.add(binPath) + } + targetConfNames.findAll { project.configurations.findByName(it) }.each { name -> + def conf = project.configurations[name] + if (conf.canBeResolved) { + conf.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (!(artifact.id.componentIdentifier instanceof ProjectComponentIdentifier)) { + def binPath = artifact.file.absolutePath + if (!processedFiles.contains(binPath)) { + def id = artifact.moduleVersion?.id + def key = id == null ? null : "${id.group}:${id.name}".toString() + def srcPath = key == null ? "NO_SOURCE" : sourceMap[key] ?: "NO_SOURCE" + println "LIB|${binPath}|${srcPath}" + processedFiles.add(binPath) + } } } } } + println "JAVALS_END" } - println "JAVALS_END" } } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java index 742eb7a3f1..5a79d3890f 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java @@ -195,12 +195,6 @@ private static void process(IProject project, String output, IProgressMonitor mo } break; } - case "SRC": { - break; - } - case "RES": { - break; - } default: JavaLanguageServerPlugin.logInfo("Unexpected value: " + elements[0]); break; 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 d68d2946dd..b70f155f82 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 @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.ByteArrayOutputStream; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; @@ -68,6 +69,9 @@ import org.eclipse.jdt.ls.core.internal.WorkspaceHelper; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.jdt.ls.core.internal.preferences.Preferences.FeatureStatus; +import org.gradle.tooling.BuildLauncher; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -879,6 +883,25 @@ public void testScalaSupportEnabled() throws Exception { } } + @Test + public void testScalaSupportTaskNoopsForNonScalaProject() throws Exception { + File projectDir = new File(getSourceProjectDirectory(), "gradle/scala/common"); + File initScript = null; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { + initScript = GradleUtils.getGradleInitScript("/gradle/scala/javals.gradle"); + BuildLauncher launcher = connection.newBuild(); + launcher.withArguments("--init-script", initScript.getAbsolutePath(), "--no-configuration-cache", "--quiet"); + launcher.forTasks("javalsCheckProject"); + launcher.setStandardOutput(outputStream); + launcher.run(); + assertEquals("", outputStream.toString().trim()); + } finally { + if (initScript != null) { + Files.deleteIfExists(initScript.toPath()); + } + } + } + @Test public void testScalaSupportDisabled() throws Exception { boolean oldScalaSupported = this.preferences.isScalaSupportEnabled(); From 77c4c0413cb6861a0fa07551c0216cd9dfc585d1 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 21 May 2026 10:23:43 +0800 Subject: [PATCH 03/21] Run javalsCheckProject from multi-project root in non-Scala test The testScalaSupportTaskNoopsForNonScalaProject test connected the Gradle Tooling API directly to the 'common' subdirectory, which has no settings.gradle, gradlew, or wrapper. Behavior in that case is environment-dependent. Connect to the multi-project root and reference the task by its qualified path (:common:javalsCheckProject), and assert the absence of the javals protocol markers (JAVALS_START / LIB|) instead of strict empty-stdout, which is brittle against incidental Gradle/Tooling output. --- .../core/internal/managers/GradleProjectImporterTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 b70f155f82..dfc0e7d7a7 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 @@ -885,16 +885,18 @@ public void testScalaSupportEnabled() throws Exception { @Test public void testScalaSupportTaskNoopsForNonScalaProject() throws Exception { - File projectDir = new File(getSourceProjectDirectory(), "gradle/scala/common"); + File projectDir = new File(getSourceProjectDirectory(), "gradle/scala"); File initScript = null; try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { initScript = GradleUtils.getGradleInitScript("/gradle/scala/javals.gradle"); BuildLauncher launcher = connection.newBuild(); launcher.withArguments("--init-script", initScript.getAbsolutePath(), "--no-configuration-cache", "--quiet"); - launcher.forTasks("javalsCheckProject"); + launcher.forTasks(":common:javalsCheckProject"); launcher.setStandardOutput(outputStream); launcher.run(); - assertEquals("", outputStream.toString().trim()); + String output = outputStream.toString(); + assertFalse(output.contains("JAVALS_START")); + assertFalse(output.contains("LIB|")); } finally { if (initScript != null) { Files.deleteIfExists(initScript.toPath()); From e83330adfd204f6ab2fb4f2be7ba5f28e635bcfb Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 21 May 2026 11:32:32 +0800 Subject: [PATCH 04/21] Run gradle compile tasks for multi-project builds when root is not imported GradleBuildSupport.compile(null) was only triggering compile tasks when a workspace project's location matched the Gradle root project directory. In multi-project builds where only the subprojects are imported as Eclipse projects (e.g. a root with only settings.gradle and several include(...) subprojects), no workspace project matched the root and the compile loop silently did nothing - so compileScala/compileGroovy/compileKotlin/compileAj never ran, leaving Java types that reference those languages unresolved. Group workspace projects by their Gradle root and pick any one of them per root to use as a connection point, then run the build from the root directory itself. Also refresh any workspace gradle project located under the gradle root so that JDT picks up class files written by the build, even when the root directory is not imported as a workspace project. --- .../internal/managers/GradleBuildSupport.java | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index 4894903e59..8908e9af2b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -519,7 +519,7 @@ public void compile(IResource resource, IProgressMonitor monitor) { return; } if (resource == null) { - Set projects = new HashSet<>(); + Map projectsByRoot = new HashMap<>(); for (IProject project : ProjectUtils.getGradleProjects()) { if (!project.isOpen()) { continue; @@ -529,16 +529,27 @@ public void compile(IResource resource, IProgressMonitor monitor) { GradleBuild gb = gradleBuild.get(); if (gb instanceof InternalGradleBuild igb) { File rootDir = igb.getBuildConfig().getProperties().getRootProjectDirectory(); - if (rootDir != null && rootDir.equals(project.getRawLocation().toFile())) { - projects.add(project); + if (rootDir == null) { + continue; + } + // Prefer the workspace project whose location IS the gradle root + // (single-project or root-imported case). Otherwise, fall back to + // the first workspace project belonging to that build so we still + // have a connection point for multi-project builds where the root + // itself is not imported. + File projectLocation = project.getRawLocation() == null ? null : project.getRawLocation().toFile(); + if (rootDir.equals(projectLocation)) { + projectsByRoot.put(rootDir, project); + } else { + projectsByRoot.putIfAbsent(rootDir, project); } } } } Map roots = new HashMap<>(); - for (IProject project : projects) { - File projectDir = project.getLocation().toFile(); - try (ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { + for (Map.Entry entry : projectsByRoot.entrySet()) { + File rootDir = entry.getKey(); + try (ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(rootDir).connect()) { GradleProject gradleProject = connection.getModel(GradleProject.class); roots.put(gradleProject.getPath(), gradleProject); } @@ -612,13 +623,20 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr .map(container -> (IProject) container) .toArray(IProject[]::new); List workspaceProjects = ProjectUtils.getGradleProjects(); + Set refreshed = new HashSet<>(); for (IProject project: projects) { - project.refreshLocal(IResource.DEPTH_INFINITE, monitor); - for (IProject wp: workspaceProjects) { - if (wp.isAccessible() && !wp.equals(project)) { - if (project.getLocation().isPrefixOf(wp.getLocation())) { - wp.refreshLocal(IResource.DEPTH_INFINITE, monitor); - } + if (refreshed.add(project)) { + project.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + } + // Also refresh any workspace gradle project located under the + // gradle root directory. This covers multi-project builds where + // the root directory itself is not imported as an Eclipse project + // (so the loop above would otherwise be a no-op). + for (IProject wp : workspaceProjects) { + if (wp.isAccessible() && wp.getLocation() != null && path.isPrefixOf(wp.getLocation())) { + if (refreshed.add(wp)) { + wp.refreshLocal(IResource.DEPTH_INFINITE, monitor); } } } From 6da4d815acc4f9aadd1ec6b7f5ec801f7cd84636 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 13:13:29 +0800 Subject: [PATCH 05/21] Fix PR-3776 integration test breakages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../jdt/ls/core/internal/managers/GradleBuildSupport.java | 4 ++-- .../ls/core/internal/managers/GradleProjectImporterTest.java | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index 8908e9af2b..d681b63f19 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -546,12 +546,12 @@ public void compile(IResource resource, IProgressMonitor monitor) { } } } - Map roots = new HashMap<>(); + Map roots = new HashMap<>(); for (Map.Entry entry : projectsByRoot.entrySet()) { File rootDir = entry.getKey(); try (ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(rootDir).connect()) { GradleProject gradleProject = connection.getModel(GradleProject.class); - roots.put(gradleProject.getPath(), gradleProject); + roots.put(rootDir, gradleProject); } } for (GradleProject gradleProject : roots.values()) { 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 dfc0e7d7a7..40b46e981a 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 @@ -73,6 +73,7 @@ import org.gradle.tooling.GradleConnector; import org.gradle.tooling.ProjectConnection; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -885,6 +886,8 @@ public void testScalaSupportEnabled() throws Exception { @Test public void testScalaSupportTaskNoopsForNonScalaProject() throws Exception { + // Gradle 8.5 can fail to compile the init script with JDK 25 (ASM/Groovy classfile support). + Assumptions.assumeTrue(Runtime.version().feature() < 25, "Skip on JDK 25+"); File projectDir = new File(getSourceProjectDirectory(), "gradle/scala"); File initScript = null; try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { From 814fc1aa99992bc360d4b26b7ba5d681b5cc9da9 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 13:46:33 +0800 Subject: [PATCH 06/21] Run qualified Gradle compile tasks per subproject Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/managers/GradleBuildSupport.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index d681b63f19..27307abdf8 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -555,13 +556,11 @@ public void compile(IResource resource, IProgressMonitor monitor) { } } for (GradleProject gradleProject : roots.values()) { - List taskNames = new ArrayList<>(); + Set taskNames = new LinkedHashSet<>(); for (String taskName : includeTasks) { - if (hasTask(gradleProject, taskName)) { - taskNames.add(taskName); - } + collectTaskPaths(gradleProject, taskName, taskNames); } - compile(gradleProject, taskNames, monitor); + compile(gradleProject, new ArrayList<>(taskNames), monitor); } } } @@ -607,10 +606,10 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr String error = errorStream.toString(); if (!error.isBlank()) { publishDiagnostics(error, new ParseOptions( - taskNames.contains(COMPILE_KOTLIN) || taskNames.contains(COMPILE_TEST_KOTLIN), - taskNames.contains(COMPILE_GROOVY) || taskNames.contains(COMPILE_TEST_GROOVY), - taskNames.contains(COMPILE_ASPECTJ) || taskNames.contains(COMPILE_TEST_ASPECTJ), - taskNames.contains(COMPILE_SCALA) || taskNames.contains(COMPILE_TEST_SCALA) + containsTask(taskNames, COMPILE_KOTLIN) || containsTask(taskNames, COMPILE_TEST_KOTLIN), + containsTask(taskNames, COMPILE_GROOVY) || containsTask(taskNames, COMPILE_TEST_GROOVY), + containsTask(taskNames, COMPILE_ASPECTJ) || containsTask(taskNames, COMPILE_TEST_ASPECTJ), + containsTask(taskNames, COMPILE_SCALA) || containsTask(taskNames, COMPILE_TEST_SCALA) ) ); } @@ -668,6 +667,26 @@ private boolean hasTask(GradleProject project, String taskName) { return false; } + private void collectTaskPaths(GradleProject project, String taskName, Set taskPaths) { + for (GradleTask task : project.getTasks()) { + if (taskName.equals(task.getName())) { + String projectPath = project.getPath(); + if (projectPath == null || ":".equals(projectPath)) { + taskPaths.add(taskName); + } else { + taskPaths.add(projectPath + ":" + taskName); + } + } + } + for (GradleProject childProject : project.getChildren()) { + collectTaskPaths(childProject, taskName, taskPaths); + } + } + + private boolean containsTask(List taskNames, String taskName) { + return taskNames.stream().anyMatch(name -> taskName.equals(name) || name.endsWith(":" + taskName)); + } + private void publishDiagnostics(String error, ParseOptions parseOptions) { JavaLanguageServerPlugin.logError("Gradle Error log: " + error); Map> diagnosticMap = new HashMap<>(); From 8eeddb2cd8e827eafbed4de9aabea127ae77a92e Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 14:38:23 +0800 Subject: [PATCH 07/21] Add Scala output dirs to Gradle classpath support --- .../gradle/scala/javals.gradle | 27 ++++++++++----- .../internal/managers/ScalaGradleSupport.java | 33 +++++++++++++++++-- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle index 033913e4b3..93496d3415 100644 --- a/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle +++ b/org.eclipse.jdt.ls.core/gradle/scala/javals.gradle @@ -1,5 +1,6 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.tasks.scala.ScalaCompile import org.gradle.jvm.JvmLibrary import org.gradle.language.base.artifact.SourcesArtifact @@ -13,6 +14,20 @@ allprojects { def targetConfNames = ['runtimeClasspath', 'testRuntimeClasspath', 'scalaRuntime'] def componentIds = [] as Set def processedFiles = [] as Set + def addLib = { file, sourcePath -> + if (file == null) return + def binPath = file.absolutePath + if (!processedFiles.contains(binPath)) { + println "LIB|${binPath}|${sourcePath ?: 'NO_SOURCE'}" + processedFiles.add(binPath) + } + } + project.tasks.withType(ScalaCompile).each { task -> + def classesDir = task.hasProperty('destinationDirectory') + ? task.destinationDirectory.get().asFile + : task.destinationDir + addLib(classesDir, "NO_SOURCE") + } targetConfNames.findAll { project.configurations.findByName(it) }.each { name -> def conf = project.configurations[name] if (conf.canBeResolved) { @@ -39,14 +54,10 @@ allprojects { if (conf.canBeResolved) { conf.resolvedConfiguration.resolvedArtifacts.each { artifact -> if (!(artifact.id.componentIdentifier instanceof ProjectComponentIdentifier)) { - def binPath = artifact.file.absolutePath - if (!processedFiles.contains(binPath)) { - def id = artifact.moduleVersion?.id - def key = id == null ? null : "${id.group}:${id.name}".toString() - def srcPath = key == null ? "NO_SOURCE" : sourceMap[key] ?: "NO_SOURCE" - println "LIB|${binPath}|${srcPath}" - processedFiles.add(binPath) - } + def id = artifact.moduleVersion?.id + def key = id == null ? null : "${id.group}:${id.name}".toString() + def srcPath = key == null ? "NO_SOURCE" : sourceMap[key] ?: "NO_SOURCE" + addLib(artifact.file, srcPath) } } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java index 5a79d3890f..4c4fecd62e 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java @@ -135,20 +135,21 @@ private void checkSourcePaths(IProject project, IProgressMonitor monitor) { } File projectDir = project.getLocation().toFile(); File initScript = null; - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (ProjectConnection connection = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) { initScript = getInitScript(); BuildLauncher launcher = connection.newBuild(); launcher.withArguments("--init-script", initScript.getAbsolutePath(), "--no-configuration-cache"); launcher.forTasks("javalsCheckProject"); launcher.setStandardOutput(outputStream); launcher.run(); - String output = outputStream.toString(); - process(project, output, monitor); } catch (Exception e) { if (Boolean.getBoolean("jdt.ls.debug")) { JavaLanguageServerPlugin.logException(e); } } finally { + process(project, outputStream.toString(), monitor); + addDefaultScalaOutputPaths(project, monitor); if (initScript != null) { try { Files.delete(initScript.toPath()); @@ -160,6 +161,32 @@ private void checkSourcePaths(IProject project, IProgressMonitor monitor) { return; } + private static void addDefaultScalaOutputPaths(IProject project, IProgressMonitor monitor) { + File projectDir = project.getLocation().toFile(); + List paths = new ArrayList<>(); + addDefaultScalaOutputPath(projectDir, "main", paths); + addDefaultScalaOutputPath(projectDir, "test", paths); + if (!paths.isEmpty()) { + IJavaProject javaProject = JavaCore.create(project); + List toAdd = getMissingPaths(javaProject, paths); + if (!toAdd.isEmpty()) { + try { + configureClasspath(javaProject, toAdd, new HashMap<>(), monitor); + } catch (JavaModelException e) { + JavaLanguageServerPlugin.logException(e); + } + } + } + } + + private static void addDefaultScalaOutputPath(File projectDir, String sourceSet, List paths) { + File sourceDir = new File(projectDir, "src/" + sourceSet + "/scala"); + File outputDir = new File(projectDir, "build/classes/scala/" + sourceSet); + if (sourceDir.isDirectory() || outputDir.exists()) { + paths.add(outputDir.getAbsolutePath()); + } + } + private File getInitScript() throws IOException { return GradleUtils.getGradleInitScript("/gradle/scala/javals.gradle"); } From b7016c0a0a56e88b49f33fe7bec35619431c9dcc Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 15:26:46 +0800 Subject: [PATCH 08/21] Ensure Scala output dirs are added without Buildship container --- .../core/internal/managers/ScalaGradleSupport.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java index 4c4fecd62e..d74441c672 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java @@ -285,13 +285,18 @@ private static List getMissingPaths(IJavaProject javaProject, List toAdd = new ArrayList<>(); for (String path : paths) { try { + Path classpathPath = new Path(path); + boolean exists = Arrays.stream(javaProject.getRawClasspath()) + .filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) + .anyMatch(entry -> Objects.equals(entry.getPath(), classpathPath)); IClasspathContainer container = JavaCore.getClasspathContainer(CONTAINER_PATH, javaProject); if (container != null) { IClasspathEntry[] entries = container.getClasspathEntries(); - Optional optional = Arrays.stream(entries).filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY).filter(entry -> Objects.equals(entry.getPath(), new Path(path))).findFirst(); - if (!optional.isPresent()) { - toAdd.add(path); - } + Optional optional = Arrays.stream(entries).filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY).filter(entry -> Objects.equals(entry.getPath(), classpathPath)).findFirst(); + exists |= optional.isPresent(); + } + if (!exists) { + toAdd.add(path); } } catch (JavaModelException e) { JavaLanguageServerPlugin.logException(e); From e0e4bb7f0dd43ed310d739f48f1eee3f610a2bbe Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 16:12:09 +0800 Subject: [PATCH 09/21] Rebuild Gradle projects after external language compile --- .../internal/managers/GradleBuildSupport.java | 8 ++++ .../internal/managers/ScalaGradleSupport.java | 37 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index 27307abdf8..18faba491d 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -43,6 +43,7 @@ import org.eclipse.buildship.core.internal.workspace.InternalGradleBuild; import org.eclipse.buildship.core.internal.workspace.WorkbenchShutdownEvent; import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; @@ -582,6 +583,7 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr JavaLanguageServerPlugin.debugTrace(message); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + boolean buildSucceeded = false; try { connection .newBuild() @@ -594,6 +596,7 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr "--continue", "--console=plain" ) .run(); + buildSucceeded = true; } catch (Exception e) { if (Boolean.getBoolean("jdt.ls.debug")) { JavaLanguageServerPlugin.logException(e); @@ -639,6 +642,11 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr } } } + if (buildSucceeded) { + for (IProject project : refreshed) { + project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); + } + } } catch (CoreException e) { if (Boolean.getBoolean("jdt.ls.debug")) { JavaLanguageServerPlugin.logException(e); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java index d74441c672..ceb46e4781 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ScalaGradleSupport.java @@ -168,7 +168,7 @@ private static void addDefaultScalaOutputPaths(IProject project, IProgressMonito addDefaultScalaOutputPath(projectDir, "test", paths); if (!paths.isEmpty()) { IJavaProject javaProject = JavaCore.create(project); - List toAdd = getMissingPaths(javaProject, paths); + List toAdd = getMissingRawClasspathPaths(javaProject, paths); if (!toAdd.isEmpty()) { try { configureClasspath(javaProject, toAdd, new HashMap<>(), monitor); @@ -283,18 +283,43 @@ private static void configureClasspath(IJavaProject javaProject, List to private static List getMissingPaths(IJavaProject javaProject, List paths) { List toAdd = new ArrayList<>(); + IClasspathContainer container; + try { + container = JavaCore.getClasspathContainer(CONTAINER_PATH, javaProject); + } catch (JavaModelException e) { + JavaLanguageServerPlugin.logException(e); + return toAdd; + } + if (container == null) { + return toAdd; + } for (String path : paths) { try { Path classpathPath = new Path(path); boolean exists = Arrays.stream(javaProject.getRawClasspath()) .filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) .anyMatch(entry -> Objects.equals(entry.getPath(), classpathPath)); - IClasspathContainer container = JavaCore.getClasspathContainer(CONTAINER_PATH, javaProject); - if (container != null) { - IClasspathEntry[] entries = container.getClasspathEntries(); - Optional optional = Arrays.stream(entries).filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY).filter(entry -> Objects.equals(entry.getPath(), classpathPath)).findFirst(); - exists |= optional.isPresent(); + IClasspathEntry[] entries = container.getClasspathEntries(); + Optional optional = Arrays.stream(entries).filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY).filter(entry -> Objects.equals(entry.getPath(), classpathPath)).findFirst(); + exists |= optional.isPresent(); + if (!exists) { + toAdd.add(path); } + } catch (JavaModelException e) { + JavaLanguageServerPlugin.logException(e); + } + } + return toAdd; + } + + private static List getMissingRawClasspathPaths(IJavaProject javaProject, List paths) { + List toAdd = new ArrayList<>(); + for (String path : paths) { + try { + Path classpathPath = new Path(path); + boolean exists = Arrays.stream(javaProject.getRawClasspath()) + .filter(entry -> entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) + .anyMatch(entry -> Objects.equals(entry.getPath(), classpathPath)); if (!exists) { toAdd.add(path); } From 0afb23cb3e13fd44732c7eaf055bbde999d39348 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 16:50:18 +0800 Subject: [PATCH 10/21] Add Scala Gradle CI diagnostics --- .../managers/GradleProjectImporterTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) 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 40b46e981a..5491370b1d 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 @@ -45,6 +45,7 @@ import org.eclipse.buildship.core.internal.configuration.ProjectConfiguration; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; @@ -52,6 +53,7 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.apt.core.util.AptConfig; +import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; @@ -872,6 +874,7 @@ public void testScalaSupportEnabled() throws Exception { waitForOtherLangs(); IProject project = ProjectUtils.getProject("app"); assertTrue(ProjectUtils.isGradleProject(project)); + dumpScalaSupportDiagnostics(project); assertNoErrors(project); IClasspathEntry[] classpathEntries = JavaCore.create(project).getRawClasspath(); boolean hasBuildLibEntry = Arrays.stream(classpathEntries).anyMatch(cpe -> cpe.getEntryKind() == IClasspathEntry.CPE_LIBRARY && cpe.getPath().toString().replace('\\', '/').contains("/build/libs/")); @@ -884,6 +887,59 @@ public void testScalaSupportEnabled() throws Exception { } } + private void dumpScalaSupportDiagnostics(IProject project) { + System.out.println("[scala-ci] ===== Scala support diagnostics start ====="); + try { + IJavaProject javaProject = JavaCore.create(project); + File projectDir = project.getLocation().toFile(); + System.out.println("[scala-ci] java.version=" + System.getProperty("java.version")); + System.out.println("[scala-ci] project=" + project.getName() + ", location=" + projectDir.getAbsolutePath()); + System.out.println("[scala-ci] scalaSupportEnabled=" + this.preferences.isScalaSupportEnabled() + ", autobuildEnabled=" + this.preferences.isAutobuildEnabled()); + System.out.println("[scala-ci] findType(org.example.App)=" + typeExists(javaProject, "org.example.App")); + System.out.println("[scala-ci] findType(org.example.App$)=" + typeExists(javaProject, "org.example.App$")); + dumpScalaClassFile(projectDir, "build/classes/scala/main/org/example/App.class"); + dumpScalaClassFile(projectDir, "build/classes/scala/main/org/example/App$.class"); + dumpScalaClassFile(projectDir, "build/classes/scala/main/org/sample/Test.class"); + dumpScalaClassFile(projectDir, "build/classes/java/main/org/sample/Test.class"); + dumpScalaClassFile(projectDir.getParentFile(), "common/build/libs/common.jar"); + dumpClasspath("raw", javaProject.getRawClasspath()); + IClasspathContainer container = JavaCore.getClasspathContainer(ScalaGradleSupport.CONTAINER_PATH, javaProject); + if (container == null) { + System.out.println("[scala-ci] Buildship container=null"); + } else { + System.out.println("[scala-ci] Buildship container path=" + container.getPath() + ", kind=" + container.getKind()); + dumpClasspath("container", container.getClasspathEntries()); + } + for (IProject workspaceProject : ProjectUtils.getGradleProjects()) { + System.out.println("[scala-ci] workspaceGradleProject=" + workspaceProject.getName() + ", accessible=" + workspaceProject.isAccessible() + ", location=" + workspaceProject.getLocation()); + } + List markers = ResourceUtils.getErrorMarkers(project); + System.out.println("[scala-ci] errorMarkerCount=" + markers.size()); + System.out.println("[scala-ci] errors=" + ResourceUtils.toString(markers)); + } catch (Exception e) { + System.out.println("[scala-ci] diagnostics failed: " + e.getClass().getName() + ": " + e.getMessage()); + e.printStackTrace(System.out); + } + System.out.println("[scala-ci] ===== Scala support diagnostics end ====="); + } + + private boolean typeExists(IJavaProject javaProject, String typeName) throws Exception { + IType type = javaProject.findType(typeName); + return type != null && type.exists(); + } + + private void dumpScalaClassFile(File baseDir, String relativePath) throws Exception { + File file = new File(baseDir, relativePath); + System.out.println("[scala-ci] fileExists " + file.getAbsolutePath() + "=" + file.exists() + (file.exists() ? ", size=" + Files.size(file.toPath()) : "")); + } + + private void dumpClasspath(String label, IClasspathEntry[] entries) { + System.out.println("[scala-ci] " + label + "ClasspathCount=" + entries.length); + for (IClasspathEntry entry : entries) { + System.out.println("[scala-ci] " + label + "Classpath kind=" + entry.getEntryKind() + ", path=" + entry.getPath() + ", output=" + entry.getOutputLocation() + ", attrs=" + Arrays.toString(entry.getExtraAttributes())); + } + } + @Test public void testScalaSupportTaskNoopsForNonScalaProject() throws Exception { // Gradle 8.5 can fail to compile the init script with JDK 25 (ASM/Groovy classfile support). From 1f557c2902dc7ee248b58ebc0b9bf0e7f3e89358 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 20:23:33 +0800 Subject: [PATCH 11/21] Run build support compile from current project manager --- .../managers/StandardProjectsManager.java | 9 +-- .../managers/GradleProjectImporterTest.java | 56 ------------------- 2 files changed, 3 insertions(+), 62 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java index c15380076f..c4b5535ed5 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java @@ -725,12 +725,9 @@ public void projectsBuildFinished(IProgressMonitor monitor) { } this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions(); new ScalaGradleSupport().cleanScalaProjects(monitor); - ProjectsManager projectsManager = JavaLanguageServerPlugin.getProjectsManager(); - if (projectsManager != null) { - projectsManager.buildSupports().forEach(bs -> { - bs.compile(null, monitor); - }); - } + buildSupports().forEach(bs -> { + bs.compile(null, monitor); + }); } @Override 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 5491370b1d..40b46e981a 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 @@ -45,7 +45,6 @@ import org.eclipse.buildship.core.internal.configuration.ProjectConfiguration; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; @@ -53,7 +52,6 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.apt.core.util.AptConfig; -import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; @@ -874,7 +872,6 @@ public void testScalaSupportEnabled() throws Exception { waitForOtherLangs(); IProject project = ProjectUtils.getProject("app"); assertTrue(ProjectUtils.isGradleProject(project)); - dumpScalaSupportDiagnostics(project); assertNoErrors(project); IClasspathEntry[] classpathEntries = JavaCore.create(project).getRawClasspath(); boolean hasBuildLibEntry = Arrays.stream(classpathEntries).anyMatch(cpe -> cpe.getEntryKind() == IClasspathEntry.CPE_LIBRARY && cpe.getPath().toString().replace('\\', '/').contains("/build/libs/")); @@ -887,59 +884,6 @@ public void testScalaSupportEnabled() throws Exception { } } - private void dumpScalaSupportDiagnostics(IProject project) { - System.out.println("[scala-ci] ===== Scala support diagnostics start ====="); - try { - IJavaProject javaProject = JavaCore.create(project); - File projectDir = project.getLocation().toFile(); - System.out.println("[scala-ci] java.version=" + System.getProperty("java.version")); - System.out.println("[scala-ci] project=" + project.getName() + ", location=" + projectDir.getAbsolutePath()); - System.out.println("[scala-ci] scalaSupportEnabled=" + this.preferences.isScalaSupportEnabled() + ", autobuildEnabled=" + this.preferences.isAutobuildEnabled()); - System.out.println("[scala-ci] findType(org.example.App)=" + typeExists(javaProject, "org.example.App")); - System.out.println("[scala-ci] findType(org.example.App$)=" + typeExists(javaProject, "org.example.App$")); - dumpScalaClassFile(projectDir, "build/classes/scala/main/org/example/App.class"); - dumpScalaClassFile(projectDir, "build/classes/scala/main/org/example/App$.class"); - dumpScalaClassFile(projectDir, "build/classes/scala/main/org/sample/Test.class"); - dumpScalaClassFile(projectDir, "build/classes/java/main/org/sample/Test.class"); - dumpScalaClassFile(projectDir.getParentFile(), "common/build/libs/common.jar"); - dumpClasspath("raw", javaProject.getRawClasspath()); - IClasspathContainer container = JavaCore.getClasspathContainer(ScalaGradleSupport.CONTAINER_PATH, javaProject); - if (container == null) { - System.out.println("[scala-ci] Buildship container=null"); - } else { - System.out.println("[scala-ci] Buildship container path=" + container.getPath() + ", kind=" + container.getKind()); - dumpClasspath("container", container.getClasspathEntries()); - } - for (IProject workspaceProject : ProjectUtils.getGradleProjects()) { - System.out.println("[scala-ci] workspaceGradleProject=" + workspaceProject.getName() + ", accessible=" + workspaceProject.isAccessible() + ", location=" + workspaceProject.getLocation()); - } - List markers = ResourceUtils.getErrorMarkers(project); - System.out.println("[scala-ci] errorMarkerCount=" + markers.size()); - System.out.println("[scala-ci] errors=" + ResourceUtils.toString(markers)); - } catch (Exception e) { - System.out.println("[scala-ci] diagnostics failed: " + e.getClass().getName() + ": " + e.getMessage()); - e.printStackTrace(System.out); - } - System.out.println("[scala-ci] ===== Scala support diagnostics end ====="); - } - - private boolean typeExists(IJavaProject javaProject, String typeName) throws Exception { - IType type = javaProject.findType(typeName); - return type != null && type.exists(); - } - - private void dumpScalaClassFile(File baseDir, String relativePath) throws Exception { - File file = new File(baseDir, relativePath); - System.out.println("[scala-ci] fileExists " + file.getAbsolutePath() + "=" + file.exists() + (file.exists() ? ", size=" + Files.size(file.toPath()) : "")); - } - - private void dumpClasspath(String label, IClasspathEntry[] entries) { - System.out.println("[scala-ci] " + label + "ClasspathCount=" + entries.length); - for (IClasspathEntry entry : entries) { - System.out.println("[scala-ci] " + label + "Classpath kind=" + entry.getEntryKind() + ", path=" + entry.getPath() + ", output=" + entry.getOutputLocation() + ", attrs=" + Arrays.toString(entry.getExtraAttributes())); - } - } - @Test public void testScalaSupportTaskNoopsForNonScalaProject() throws Exception { // Gradle 8.5 can fail to compile the init script with JDK 25 (ASM/Groovy classfile support). From 6b1ed5f9d500a82b519a015918f04626ca4a9caa Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 21:11:36 +0800 Subject: [PATCH 12/21] Force rebuild after Gradle external language compile --- .../jdt/ls/core/internal/managers/GradleBuildSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index 18faba491d..117f8ac570 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -644,7 +644,7 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr } if (buildSucceeded) { for (IProject project : refreshed) { - project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); + project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); } } } catch (CoreException e) { From 1131ac436ea61b7d2d8f81bb2352f068b0a2fd27 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 21:51:12 +0800 Subject: [PATCH 13/21] Align Scala fixture subproject Java target --- .../projects/gradle/scala/common/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle index fa88eeb1c2..cf59a34aa8 100644 --- a/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle +++ b/org.eclipse.jdt.ls.tests/projects/gradle/scala/common/build.gradle @@ -5,3 +5,6 @@ plugins { repositories { mavenCentral() } + +sourceCompatibility = '21' +targetCompatibility = '21' From 6f64d628e52f5b2c96daf08fe3d577552446d8bb Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 18 Jun 2026 22:24:58 +0800 Subject: [PATCH 14/21] Refresh command registry when listing commands --- .../internal/handlers/WorkspaceExecuteCommandHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index cd29e26d85..9fe4f19651 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -152,7 +152,7 @@ private synchronized Collection getDelegateCom } public Set getStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getStaticCommands()); @@ -161,7 +161,7 @@ public Set getStaticCommands() { } public Set getNonStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getNonStaticCommands()); @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); @@ -194,7 +194,7 @@ public Object executeCommand(ExecuteCommandParams params, IProgressMonitor monit throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, errorMessage, null)); } - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Collection candidates = handlers.stream().filter(desc -> desc.getAllCommands().contains(params.getCommand())).collect(Collectors.toSet()); //no cancellation here but it's super fast so it's ok. From 80a965ee292e6fd1703e8c5024fe4b14219d77cb Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 07:53:15 +0800 Subject: [PATCH 15/21] Preserve command handler cache during execution --- .../internal/handlers/WorkspaceExecuteCommandHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index 9fe4f19651..48f69852fd 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -152,7 +152,7 @@ private synchronized Collection getDelegateCom } public Set getStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getStaticCommands()); @@ -161,7 +161,7 @@ public Set getStaticCommands() { } public Set getNonStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getNonStaticCommands()); @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); From dda8e81c3c6348a358b7877094cb7685865d4aad Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 07:53:37 +0800 Subject: [PATCH 16/21] Refresh command listing without resetting handlers --- .../core/internal/handlers/WorkspaceExecuteCommandHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index 48f69852fd..1c1d7d5e29 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); From 603fe8432e9baa9eb32da379da03456abc936722 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 08:28:16 +0800 Subject: [PATCH 17/21] Revert "Refresh command registry when listing commands" This reverts commit 6f64d628e52f5b2c96daf08fe3d577552446d8bb. --- .../internal/handlers/WorkspaceExecuteCommandHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index 1c1d7d5e29..cd29e26d85 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); @@ -194,7 +194,7 @@ public Object executeCommand(ExecuteCommandParams params, IProgressMonitor monit throw new ResponseErrorException(new ResponseError(ResponseErrorCode.InvalidParams, errorMessage, null)); } - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Collection candidates = handlers.stream().filter(desc -> desc.getAllCommands().contains(params.getCommand())).collect(Collectors.toSet()); //no cancellation here but it's super fast so it's ok. From 98221c95bc63da892cf6308836f0532deacf79cb Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 08:28:16 +0800 Subject: [PATCH 18/21] Revert "Preserve command handler cache during execution" This reverts commit 80a965ee292e6fd1703e8c5024fe4b14219d77cb. --- .../internal/handlers/WorkspaceExecuteCommandHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index cd29e26d85..eeb84e6bc3 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -152,7 +152,7 @@ private synchronized Collection getDelegateCom } public Set getStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getStaticCommands()); @@ -161,7 +161,7 @@ public Set getStaticCommands() { } public Set getNonStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getNonStaticCommands()); @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(); + Collection handlers = getDelegateCommandHandlerDescriptors(true); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); From 769987b502c4a4bdb767add0612fea62bd4ef47f Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 08:28:16 +0800 Subject: [PATCH 19/21] Revert "Refresh command listing without resetting handlers" This reverts commit dda8e81c3c6348a358b7877094cb7685865d4aad. --- .../core/internal/handlers/WorkspaceExecuteCommandHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index eeb84e6bc3..3849725583 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -170,7 +170,7 @@ public Set getNonStaticCommands() { } public Set getAllCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getAllCommands()); From f82b94fd7aded8753c1989e236363a166a797159 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Fri, 19 Jun 2026 08:28:32 +0800 Subject: [PATCH 20/21] Wait for dynamic command registry updates in test --- .../handlers/WorkspaceExecuteCommandHandlerTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandlerTest.java index 9c2ad025d0..2229e6d0c3 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandlerTest.java @@ -109,7 +109,7 @@ public void testRegistryEventListener() throws Exception { installedBundle.loadClass("jdt.ls.extension.Activator"); assertEquals(Bundle.ACTIVE, installedBundle.getState()); - extensionCommands = WorkspaceExecuteCommandHandler.getInstance().getAllCommands(); + extensionCommands = waitForCommands("jdt.ls.extension.command2", "jdt.ls.extension.command3"); assertTrue(extensionCommands.contains("jdt.ls.extension.command2")); assertTrue(extensionCommands.contains("jdt.ls.extension.command3")); assertFalse(extensionCommands.contains("jdt.ls.extension.command1")); @@ -118,4 +118,13 @@ public void testRegistryEventListener() throws Exception { installedBundle.uninstall(); } } + + private Set waitForCommands(String... commands) throws InterruptedException { + Set extensionCommands = WorkspaceExecuteCommandHandler.getInstance().getAllCommands(); + for (int i = 0; i < 20 && !extensionCommands.containsAll(Set.of(commands)); i++) { + Thread.sleep(100); + extensionCommands = WorkspaceExecuteCommandHandler.getInstance().getAllCommands(); + } + return extensionCommands; + } } From c1b602f7d39a1155fe51afdf0d9728d7148a0634 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 22 Jun 2026 09:46:33 +0800 Subject: [PATCH 21/21] Remove unnecessary rebuild and command handler changes --- .../internal/handlers/WorkspaceExecuteCommandHandler.java | 4 ++-- .../jdt/ls/core/internal/managers/GradleBuildSupport.java | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java index 3849725583..cd29e26d85 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceExecuteCommandHandler.java @@ -152,7 +152,7 @@ private synchronized Collection getDelegateCom } public Set getStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getStaticCommands()); @@ -161,7 +161,7 @@ public Set getStaticCommands() { } public Set getNonStaticCommands() { - Collection handlers = getDelegateCommandHandlerDescriptors(true); + Collection handlers = getDelegateCommandHandlerDescriptors(); Set commands = new HashSet<>(); for (DelegateCommandHandlerDescriptor handler : handlers) { commands.addAll(handler.getNonStaticCommands()); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java index 117f8ac570..27307abdf8 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/GradleBuildSupport.java @@ -43,7 +43,6 @@ import org.eclipse.buildship.core.internal.workspace.InternalGradleBuild; import org.eclipse.buildship.core.internal.workspace.WorkbenchShutdownEvent; import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; @@ -583,7 +582,6 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr JavaLanguageServerPlugin.debugTrace(message); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); - boolean buildSucceeded = false; try { connection .newBuild() @@ -596,7 +594,6 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr "--continue", "--console=plain" ) .run(); - buildSucceeded = true; } catch (Exception e) { if (Boolean.getBoolean("jdt.ls.debug")) { JavaLanguageServerPlugin.logException(e); @@ -642,11 +639,6 @@ private void compile(GradleProject gradleProject, List taskNames, IProgr } } } - if (buildSucceeded) { - for (IProject project : refreshed) { - project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); - } - } } catch (CoreException e) { if (Boolean.getBoolean("jdt.ls.debug")) { JavaLanguageServerPlugin.logException(e);