From 2bf1a06057348cf8d0b867dcd77a65d19a10e978 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 2 Apr 2026 13:00:18 -0700 Subject: [PATCH 1/3] Pass environment to refresh AOT maven/gralde command on the client Signed-off-by: BoykoAlex --- .../boot/java/BuildCommandProvider.java | 15 +++- .../java/DefaultBuildCommandProvider.java | 72 ++++++++++++++----- .../boot/java/VSCodeBuildCommandProvider.java | 18 ++++- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BuildCommandProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BuildCommandProvider.java index a5223ec267..01f4ff1a8f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BuildCommandProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BuildCommandProvider.java @@ -10,13 +10,26 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; +import java.util.LinkedHashMap; +import java.util.Map; + import org.eclipse.lsp4j.Command; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.protocol.java.Jre; public interface BuildCommandProvider { Command executeMavenGoal(IJavaProject project, String goal); Command executeGradleBuild(IJavaProject project, String command); - + + static Map buildEnv(IJavaProject project) { + Map env = new LinkedHashMap<>(); + Jre jre = project.getClasspath().getJre(); + if (jre != null && jre.installationPath() != null) { + env.put("JAVA_HOME", jre.installationPath()); + } + return env; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/DefaultBuildCommandProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/DefaultBuildCommandProvider.java index effecc2f68..e81b68c2b0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/DefaultBuildCommandProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/DefaultBuildCommandProvider.java @@ -14,7 +14,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -22,7 +26,7 @@ import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.util.OS; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; - +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; public class DefaultBuildCommandProvider implements BuildCommandProvider { @@ -38,9 +42,10 @@ public DefaultBuildCommandProvider(SimpleLanguageServer server) { server.onCommand(CMD_EXEC_MAVEN_GOAL, params -> { String pomPath = extractString(params.getArguments().get(0)); String goal = extractString(params.getArguments().get(1)); + Map env = params.getArguments().size() > 2 ? extractEnv(params.getArguments().get(2)) : null; return CompletableFuture.runAsync(() -> { try { - executeMaven(Paths.get(pomPath), goal.trim().split("\\s+")).get(); + executeMaven(Paths.get(pomPath), goal.trim().split("\\s+"), env).get(); } catch (Exception e) { throw new CompletionException(e); } @@ -51,9 +56,10 @@ public DefaultBuildCommandProvider(SimpleLanguageServer server) { server.onCommand(CMD_EXEC_GRADLE_BUILD, params -> { String gradleBuildPath = extractString(params.getArguments().get(0)); String command = extractString(params.getArguments().get(1)); + Map env = params.getArguments().size() > 2 ? extractEnv(params.getArguments().get(2)) : null; return CompletableFuture.runAsync(() -> { try { - executeGradle(Paths.get(gradleBuildPath), command.trim().split("\\s+")).get(); + executeGradle(Paths.get(gradleBuildPath), command.trim().split("\\s+"), env).get(); } catch (Exception e) { throw new CompletionException(e); } @@ -66,7 +72,13 @@ public Command executeMavenGoal(IJavaProject project, String goal) { Command cmd = new Command(); cmd.setCommand(CMD_EXEC_MAVEN_GOAL); cmd.setTitle("Execute Maven Goal"); - cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal)); + List args = new ArrayList<>(List.of( + Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal)); + Map env = BuildCommandProvider.buildEnv(project); + if (!env.isEmpty()) { + args.add(env); + } + cmd.setArguments(args); return cmd; } @@ -75,7 +87,13 @@ public Command executeGradleBuild(IJavaProject project, String command) { Command cmd = new Command(); cmd.setCommand(CMD_EXEC_GRADLE_BUILD); cmd.setTitle("Execute Gradle Build"); - cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command)); + List args = new ArrayList<>(List.of( + Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command)); + Map env = BuildCommandProvider.buildEnv(project); + if (env != null && !env.isEmpty()) { + args.add(env); + } + cmd.setArguments(args); return cmd; } @@ -83,17 +101,30 @@ private static String extractString(Object o) { return o instanceof JsonPrimitive ? ((JsonPrimitive) o).getAsString() : o.toString(); } - private CompletableFuture executeMaven(Path pom, String[] goal) { + private static Map extractEnv(Object o) { + Map env = new LinkedHashMap<>(); + if (o instanceof JsonObject jo) { + jo.entrySet().forEach(e -> env.put(e.getKey(), e.getValue().getAsString())); + } + return env; + } + + private CompletableFuture executeMaven(Path pom, String[] goal, Map env) { synchronized(MAVEN_LOCK) { - String[] cmd = new String[1 + goal.length]; Path projectPath = pom.getParent(); Path mvnw = projectPath.resolve(OS.isWindows() ? "mvnw.cmd" : "mvnw"); - cmd[0] = Files.isRegularFile(mvnw) ? mvnw.toFile().toString() : "mvn"; - System.arraycopy(goal, 0, cmd, 1, goal.length); + List cmdList = new ArrayList<>(); + cmdList.add(Files.isRegularFile(mvnw) ? mvnw.toFile().toString() : "mvn"); + cmdList.addAll(Arrays.asList(goal)); try { - return Runtime.getRuntime().exec(cmd, null, projectPath.toFile()).onExit().thenAccept(process -> { + ProcessBuilder pb = new ProcessBuilder(cmdList); + pb.directory(projectPath.toFile()); + if (env != null && !env.isEmpty()) { + pb.environment().putAll(env); + } + return pb.start().onExit().thenAccept(process -> { if (process.exitValue() != 0) { - throw new CompletionException("Failed to execute Maven goal", new IllegalStateException("Errors running maven command: %s".formatted(String.join(" ", cmd)))); + throw new CompletionException("Failed to execute Maven goal", new IllegalStateException("Errors running maven command: %s".formatted(String.join(" ", cmdList)))); } }); } catch (IOException e) { @@ -102,16 +133,21 @@ private CompletableFuture executeMaven(Path pom, String[] goal) { } } - private CompletableFuture executeGradle(Path gradleBuildPath, String[] command) { - String[] cmd = new String[1 + command.length]; + private CompletableFuture executeGradle(Path gradleBuildPath, String[] command, Map env) { Path projectPath = gradleBuildPath.getParent(); - Path mvnw = projectPath.resolve(OS.isWindows() ? "gradlew.cmd" : "gradlew"); - cmd[0] = Files.isRegularFile(mvnw) ? mvnw.toFile().toString() : "gradle"; - System.arraycopy(command, 0, cmd, 1, command.length); + Path gradlew = projectPath.resolve(OS.isWindows() ? "gradlew.cmd" : "gradlew"); + List cmdList = new ArrayList<>(); + cmdList.add(Files.isRegularFile(gradlew) ? gradlew.toFile().toString() : "gradle"); + cmdList.addAll(Arrays.asList(command)); try { - return Runtime.getRuntime().exec(cmd, null, projectPath.toFile()).onExit().thenAccept(process -> { + ProcessBuilder pb = new ProcessBuilder(cmdList); + pb.directory(projectPath.toFile()); + if (env != null && !env.isEmpty()) { + pb.environment().putAll(env); + } + return pb.start().onExit().thenAccept(process -> { if (process.exitValue() != 0) { - throw new CompletionException("Failed to execute Gradle build", new IllegalStateException("Errors running gradle command: %s".formatted(String.join(" ", cmd)))); + throw new CompletionException("Failed to execute Gradle build", new IllegalStateException("Errors running gradle command: %s".formatted(String.join(" ", cmdList)))); } }); } catch (IOException e) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java index e3096de82d..1b89c54454 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java @@ -11,7 +11,9 @@ package org.springframework.ide.vscode.boot.java; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.eclipse.lsp4j.Command; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -23,7 +25,13 @@ public Command executeMavenGoal(IJavaProject project, String goal) { Command cmd = new Command(); cmd.setCommand("maven.goal.custom"); cmd.setTitle("Execute Maven Goal"); - cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal)); + List args = new ArrayList<>(List.of( + Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal)); + Map env = BuildCommandProvider.buildEnv(project); + if (env != null && !env.isEmpty()) { + args.add(env); + } + cmd.setArguments(args); return cmd; } @@ -32,7 +40,13 @@ public Command executeGradleBuild(IJavaProject project, String command) { Command cmd = new Command(); cmd.setCommand("gradle.runBuild"); cmd.setTitle("Execute Gradle Build"); - cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command)); + List args = new ArrayList<>(List.of( + Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command)); + Map env = BuildCommandProvider.buildEnv(project); + if (env != null && !env.isEmpty()) { + args.add(env); + } + cmd.setArguments(args); return cmd; } From e531b17767276973ac9e8fc1cca6d9f00d7612aa Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Mon, 20 Apr 2026 16:49:05 -0700 Subject: [PATCH 2/3] Adjust tests Signed-off-by: BoykoAlex --- .../test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java | 2 +- .../test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java | 2 +- .../DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java index 50920aae03..51cbf36b03 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java @@ -97,7 +97,7 @@ void noCodeLensOverMethodWithQueryAnnotation() throws Exception { assertEquals("Go To Implementation", cls.get(0).getCommand().getTitle()); assertEquals(1, cls.get(0).getCommand().getArguments().size()); assertEquals("Refresh AOT Metadata", cls.get(1).getCommand().getTitle()); - assertEquals(2, cls.get(1).getCommand().getArguments().size()); + assertEquals(3, cls.get(1).getCommand().getArguments().size()); } @Test diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java index f7d7dd37b9..60e9ce8d7c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJpaTest.java @@ -88,7 +88,7 @@ void noCodeLensOverMethodWithQueryAnnotation() throws Exception { assertEquals("Go To Implementation", cls.get(0).getCommand().getTitle()); assertEquals(1, cls.get(0).getCommand().getArguments().size()); assertEquals("Refresh AOT Metadata", cls.get(1).getCommand().getTitle()); - assertEquals(2, cls.get(1).getCommand().getArguments().size()); + assertEquals(3, cls.get(1).getCommand().getArguments().size()); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java index 6e2999abae..2c018960b3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderMongoDbTest.java @@ -100,7 +100,7 @@ void noCodeLensOverMethodWithQueryAnnotation() throws Exception { assertEquals("Go To Implementation", cls.get(0).getCommand().getTitle()); assertEquals(1, cls.get(0).getCommand().getArguments().size()); assertEquals("Refresh AOT Metadata", cls.get(1).getCommand().getTitle()); - assertEquals(2, cls.get(1).getCommand().getArguments().size()); + assertEquals(3, cls.get(1).getCommand().getArguments().size()); } } From 6ce0aa7efa12009ee87589822815a25d3d4a0046 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Mon, 20 Apr 2026 18:12:20 -0700 Subject: [PATCH 3/3] Polish Signed-off-by: BoykoAlex --- .../vscode/boot/java/VSCodeBuildCommandProvider.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java index 1b89c54454..a56aec049e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java @@ -25,8 +25,9 @@ public Command executeMavenGoal(IJavaProject project, String goal) { Command cmd = new Command(); cmd.setCommand("maven.goal.custom"); cmd.setTitle("Execute Maven Goal"); - List args = new ArrayList<>(List.of( - Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal)); + List args = new ArrayList<>(3); + args.add(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString()); + args.add(goal); Map env = BuildCommandProvider.buildEnv(project); if (env != null && !env.isEmpty()) { args.add(env); @@ -40,8 +41,9 @@ public Command executeGradleBuild(IJavaProject project, String command) { Command cmd = new Command(); cmd.setCommand("gradle.runBuild"); cmd.setTitle("Execute Gradle Build"); - List args = new ArrayList<>(List.of( - Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command)); + List args = new ArrayList<>(3); + args.add(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString()); + args.add(command); Map env = BuildCommandProvider.buildEnv(project); if (env != null && !env.isEmpty()) { args.add(env);