diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF
index d285908bee..1c23426b8c 100644
--- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF
+++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF
@@ -26,7 +26,9 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0",
org.eclipse.swt,
com.google.gson,
org.eclipse.m2e.launching,
- org.eclipse.m2e.core
+ org.eclipse.m2e.core,
+ org.eclipse.buildship.core,
+ org.gradle.toolingapi
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Export-Package: org.springframework.tooling.ls.eclipse.commons,
diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/plugin.xml b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/plugin.xml
index 466b0b337c..1187dfb30a 100644
--- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/plugin.xml
+++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/plugin.xml
@@ -111,6 +111,10 @@
class="org.springframework.tooling.ls.eclipse.commons.commands.ExecuteMavenGoalHandler"
commandId="maven.goal.custom">
+
+
@@ -201,7 +205,23 @@
+ name="Execute Maven Goal">
+
+
+
+
+
+
{
+ try {
+ return build.withConnection(conn -> conn.getModel(EclipseProject.class),
+ new NullProgressMonitor());
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failed to get Gradle EclipseProject model for project: " + project.getName(), e);
+ }
+ }).get();
+
+ File rootDir = HierarchicalElementUtils.getRoot(gradleProject).getProjectDirectory();
+ File workingDir = gradleProject.getProjectDirectory();
+ GradleRunConfigurationAttributes configurationAttributes = getRunConfigurationAttributes(rootDir,
+ workingDir, Arrays.asList(task.split("\\s+")));
+
+ // create/reuse a launch configuration for the given attributes
+ ILaunchConfiguration launchConfig = CorePlugin.gradleLaunchConfigurationManager()
+ .getOrCreateRunConfiguration(configurationAttributes);
+
+ DebugUITools.launch(launchConfig, ILaunchManager.RUN_MODE);
+ } catch (Exception e) {
+ throw new ExecutionException("Failed to execute Maven Goal command", e);
+ }
+ }
+ throw new ExecutionException("Maven Goal Execution command is invalid");
+ }
+
+ private static GradleRunConfigurationAttributes getRunConfigurationAttributes(File rootDir, File workingDir,
+ List tasks) {
+ BuildConfiguration buildConfig = CorePlugin.configurationManager().loadBuildConfiguration(rootDir);
+ return new GradleRunConfigurationAttributes(tasks, projectDirectoryExpression(workingDir),
+ buildConfig.getGradleDistribution().toString(),
+ gradleUserHomeExpression(buildConfig.getGradleUserHome()),
+ javaHomeExpression(buildConfig.getJavaHome()), buildConfig.getJvmArguments(),
+ buildConfig.getArguments(), buildConfig.isShowExecutionsView(), buildConfig.isShowExecutionsView(),
+ buildConfig.isOverrideWorkspaceSettings(), buildConfig.isOfflineMode(),
+ buildConfig.isBuildScansEnabled());
+ }
+
+ private static String projectDirectoryExpression(File rootProjectDir) {
+ // return the directory as an expression if the project is part of the
+ // workspace, otherwise
+ // return the absolute path of the project directory available on the Eclipse
+ // project model
+ Optional project = CorePlugin.workspaceOperations().findProjectByLocation(rootProjectDir);
+ if (project.isPresent()) {
+ return ExpressionUtils.encodeWorkspaceLocation(project.get());
+ } else {
+ return rootProjectDir.getAbsolutePath();
+ }
+ }
+
+ private static String gradleUserHomeExpression(File gradleUserHome) {
+ return gradleUserHome == null ? "" : gradleUserHome.getAbsolutePath();
+ }
+
+ private static String javaHomeExpression(File javaHome) {
+ return javaHome == null ? "" : javaHome.getAbsolutePath();
+ }
+}
diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/commands/ExecuteMavenGoalHandler.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/commands/ExecuteMavenGoalHandler.java
index a547d55eab..d04b7be77b 100644
--- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/commands/ExecuteMavenGoalHandler.java
+++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/commands/ExecuteMavenGoalHandler.java
@@ -52,7 +52,6 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
try {
String pomPath = (String) cmd.getArguments().get(0);
String goal = (String) cmd.getArguments().get(1);
- System.out.println("Project: '%s', goal: '%s'".formatted(pomPath, goal));
IResource pomFile = LSPEclipseUtils.findResourceFor(Paths.get(pomPath).toUri());
ILaunchConfiguration launchConfig = createLaunchConfiguration(pomFile.getParent(), goal);
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 4b9cbb08e9..a5223ec267 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
@@ -17,4 +17,6 @@ public interface BuildCommandProvider {
Command executeMavenGoal(IJavaProject project, String goal);
+ Command executeGradleBuild(IJavaProject project, String command);
+
}
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 7ab0b4759e..effecc2f68 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
@@ -28,16 +28,32 @@
public class DefaultBuildCommandProvider implements BuildCommandProvider {
private static final String CMD_EXEC_MAVEN_GOAL = "sts.maven.goal";
+ private static final String CMD_EXEC_GRADLE_BUILD = "sts.gradle.build";
private static final Object MAVEN_LOCK = new Object();
public DefaultBuildCommandProvider(SimpleLanguageServer server) {
+
+ // Execute Maven Goal
server.onCommand(CMD_EXEC_MAVEN_GOAL, params -> {
String pomPath = extractString(params.getArguments().get(0));
String goal = extractString(params.getArguments().get(1));
return CompletableFuture.runAsync(() -> {
try {
- mavenRegenerateMetadata(Paths.get(pomPath), goal.trim().split("\\s+")).get();
+ executeMaven(Paths.get(pomPath), goal.trim().split("\\s+")).get();
+ } catch (Exception e) {
+ throw new CompletionException(e);
+ }
+ });
+ });
+
+ // Execute Gradle Build
+ server.onCommand(CMD_EXEC_GRADLE_BUILD, params -> {
+ String gradleBuildPath = extractString(params.getArguments().get(0));
+ String command = extractString(params.getArguments().get(1));
+ return CompletableFuture.runAsync(() -> {
+ try {
+ executeGradle(Paths.get(gradleBuildPath), command.trim().split("\\s+")).get();
} catch (Exception e) {
throw new CompletionException(e);
}
@@ -54,11 +70,20 @@ public Command executeMavenGoal(IJavaProject project, String goal) {
return cmd;
}
+ @Override
+ 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));
+ return cmd;
+ }
+
private static String extractString(Object o) {
return o instanceof JsonPrimitive ? ((JsonPrimitive) o).getAsString() : o.toString();
}
- private CompletableFuture mavenRegenerateMetadata(Path pom, String[] goal) {
+ private CompletableFuture executeMaven(Path pom, String[] goal) {
synchronized(MAVEN_LOCK) {
String[] cmd = new String[1 + goal.length];
Path projectPath = pom.getParent();
@@ -77,5 +102,20 @@ private CompletableFuture mavenRegenerateMetadata(Path pom, String[] goal)
}
}
-
+ private CompletableFuture executeGradle(Path gradleBuildPath, String[] command) {
+ String[] cmd = new String[1 + command.length];
+ 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);
+ try {
+ return Runtime.getRuntime().exec(cmd, null, projectPath.toFile()).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))));
+ }
+ });
+ } catch (IOException e) {
+ throw new CompletionException(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 842acac34a..e3096de82d 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
@@ -27,4 +27,13 @@ public Command executeMavenGoal(IJavaProject project, String goal) {
return cmd;
}
+ @Override
+ 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));
+ return cmd;
+ }
+
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java
index f32d32aad0..28f64b9406 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java
@@ -39,7 +39,6 @@
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
-import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.AddAnnotationOverMethod;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -185,12 +184,10 @@ private List createCodeLenses(IJavaProject project, MethodDeclaration
}
private Optional createRefreshCodeLens(IJavaProject project, String title, Range range) {
- if (ProjectBuild.MAVEN_PROJECT_TYPE.equals(project.getProjectBuild().getType())) {
- Command refreshCmd = repositoryMetadataService.regenerateMetadataCommand(project);
+ return repositoryMetadataService.regenerateMetadataCommand(project).map(refreshCmd -> {
refreshCmd.setTitle(title);
- return Optional.of(new CodeLens(range, refreshCmd, null));
- }
- return Optional.empty();
+ return new CodeLens(range, refreshCmd, null);
+ });
}
static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, DataRepositoryModule module, IDataRepositoryAotMethodMetadata methodMetadata) {
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java
index bbd5af46d6..7b3fc45be4 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java
@@ -137,10 +137,20 @@ public DataRepositoryAotMetadataService(FileObserver fileObserver, JavaProjectFi
public Optional getRepositoryMetadata(IJavaProject project, String repositoryType) {
String metadataFilePath = repositoryType.replace('.', '/') + ".json";
- return IClasspathUtil.getOutputFolders(project.getClasspath())
- .map(outputFolder -> outputFolder.getParentFile().toPath().resolve("spring-aot/main/resources/").resolve(metadataFilePath))
- .findFirst()
- .flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
+ switch (project.getProjectBuild().getType()) {
+ case ProjectBuild.MAVEN_PROJECT_TYPE:
+ return IClasspathUtil.getOutputFolders(project.getClasspath())
+ .map(outputFolder -> outputFolder.getParentFile().toPath().resolve("spring-aot/main/resources/").resolve(metadataFilePath))
+ .findFirst()
+ .flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
+ case ProjectBuild.GRADLE_PROJECT_TYPE:
+ return IClasspathUtil.getSourceFolders(project.getClasspath())
+ .filter(f -> f.isDirectory() && "aotResources".equals(f.getName()))
+ .findFirst()
+ .map(f -> f.toPath().resolve(metadataFilePath))
+ .flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
+ }
+ return Optional.empty();
}
private Optional readMetadataFile(Path filePath) {
@@ -154,7 +164,7 @@ private Optional readMetadataFile(Path filePath) {
return Optional.empty();
}
- public Command regenerateMetadataCommand(IJavaProject jp) {
+ Optional regenerateMetadataCommand(IJavaProject jp) {
switch (jp.getProjectBuild().getType()) {
case ProjectBuild.MAVEN_PROJECT_TYPE:
List goal = new ArrayList<>();
@@ -170,9 +180,24 @@ public Command regenerateMetadataCommand(IJavaProject jp) {
goal.add("compile");
}
goal.add("org.springframework.boot:spring-boot-maven-plugin:process-aot");
- return buildCmds.executeMavenGoal(jp, String.join(" ", goal));
+ return Optional.ofNullable(buildCmds.executeMavenGoal(jp, String.join(" ", goal)));
+// case ProjectBuild.GRADLE_PROJECT_TYPE:
+// List command = new ArrayList<>();
+// if (!IClasspathUtil.getOutputFolders(jp.getClasspath()).map(f -> f.toPath()).filter(Files::isDirectory).flatMap(d -> {
+// try {
+// return Files.walk(d);
+// } catch (IOException e) {
+// return Stream.empty();
+// }
+// }).anyMatch(f -> Files.isRegularFile(f) && f.getFileName().toString().endsWith(".class"))) {
+// // Check if source is compiled by checking that all output folders exist
+// // If not compiled then add `build` task
+// command.add("build");
+// }
+// command.add("processAot");
+// return Optional.ofNullable(buildCmds.executeGradleBuild(jp, String.join(" ", command)));
}
- return null;
+ return Optional.empty();
}
public void addListener(Consumer> listener) {