Skip to content

Commit 212b758

Browse files
authored
Spring Data AOT metadata Gradle (#1721)
* WIP: Refresh AOT data command for Eclipse Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * Basic Gradle support for Spring Data AOT Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> --------- Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent c583925 commit 212b758

File tree

9 files changed

+227
-19
lines changed

9 files changed

+227
-19
lines changed

eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0",
2626
org.eclipse.swt,
2727
com.google.gson,
2828
org.eclipse.m2e.launching,
29-
org.eclipse.m2e.core
29+
org.eclipse.m2e.core,
30+
org.eclipse.buildship.core,
31+
org.gradle.toolingapi
3032
Bundle-RequiredExecutionEnvironment: JavaSE-21
3133
Bundle-ActivationPolicy: lazy
3234
Export-Package: org.springframework.tooling.ls.eclipse.commons,

eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/plugin.xml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@
111111
class="org.springframework.tooling.ls.eclipse.commons.commands.ExecuteMavenGoalHandler"
112112
commandId="maven.goal.custom">
113113
</handler>
114+
<handler
115+
class="org.springframework.tooling.ls.eclipse.commons.commands.ExecuteGradleTaskHandler"
116+
commandId="gradle.runBuild">
117+
</handler>
114118
</extension>
115119
<extension
116120
point="org.eclipse.ui.commands">
@@ -201,7 +205,23 @@
201205
</command>
202206
<command
203207
id="maven.goal.custom"
204-
name="Explain with AI">
208+
name="Execute Maven Goal">
209+
<commandParameter
210+
id="org.eclipse.lsp4e.path.param"
211+
name="Resource Path (unnecessary, only to make lsp4e happy)"
212+
optional="true"
213+
typeId="org.eclipse.lsp4e.pathParameterType">
214+
</commandParameter>
215+
<commandParameter
216+
id="org.eclipse.lsp4e.command.param"
217+
name="Command id (unnecessary, only to make lsp4e happy)"
218+
optional="true"
219+
typeId="org.eclipse.lsp4e.commandParameterType">
220+
</commandParameter>
221+
</command>
222+
<command
223+
id="gradle.runBuild"
224+
name="Execute Gradle Build">
205225
<commandParameter
206226
id="org.eclipse.lsp4e.path.param"
207227
name="Resource Path (unnecessary, only to make lsp4e happy)"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.tooling.ls.eclipse.commons.commands;
12+
13+
import java.io.File;
14+
import java.nio.file.Paths;
15+
import java.util.Arrays;
16+
import java.util.List;
17+
18+
import org.gradle.tooling.model.eclipse.EclipseProject;
19+
import org.eclipse.buildship.core.GradleCore;
20+
import org.eclipse.buildship.core.internal.CorePlugin;
21+
import org.eclipse.buildship.core.internal.configuration.BuildConfiguration;
22+
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
23+
import org.eclipse.buildship.core.internal.util.gradle.HierarchicalElementUtils;
24+
import org.eclipse.buildship.core.internal.util.variable.ExpressionUtils;
25+
import org.eclipse.core.commands.AbstractHandler;
26+
import org.eclipse.core.commands.ExecutionEvent;
27+
import org.eclipse.core.commands.ExecutionException;
28+
import org.eclipse.core.resources.IProject;
29+
import org.eclipse.core.resources.IResource;
30+
import org.eclipse.core.runtime.NullProgressMonitor;
31+
import org.eclipse.debug.core.ILaunchConfiguration;
32+
import org.eclipse.debug.core.ILaunchManager;
33+
import org.eclipse.debug.ui.DebugUITools;
34+
import org.eclipse.lsp4e.LSPEclipseUtils;
35+
import org.eclipse.lsp4e.command.LSPCommandHandler;
36+
import org.eclipse.lsp4j.Command;
37+
38+
import com.google.common.base.Optional;
39+
import com.google.gson.Gson;
40+
41+
@SuppressWarnings("restriction")
42+
public class ExecuteGradleTaskHandler extends AbstractHandler {
43+
44+
@Override
45+
public Object execute(ExecutionEvent event) throws ExecutionException {
46+
Command cmd = new Gson().fromJson(event.getParameter(LSPCommandHandler.LSP_COMMAND_PARAMETER_ID),
47+
Command.class);
48+
if (cmd != null) {
49+
try {
50+
String buildGradlePath = (String) cmd.getArguments().get(0);
51+
String task = (String) cmd.getArguments().get(1);
52+
53+
IResource buildGradle = LSPEclipseUtils.findResourceFor(Paths.get(buildGradlePath).toUri());
54+
IProject project = buildGradle.getProject();
55+
EclipseProject gradleProject = GradleCore.getWorkspace().getBuild(project).map(build -> {
56+
try {
57+
return build.withConnection(conn -> conn.getModel(EclipseProject.class),
58+
new NullProgressMonitor());
59+
} catch (Exception e) {
60+
throw new RuntimeException(
61+
"Failed to get Gradle EclipseProject model for project: " + project.getName(), e);
62+
}
63+
}).get();
64+
65+
File rootDir = HierarchicalElementUtils.getRoot(gradleProject).getProjectDirectory();
66+
File workingDir = gradleProject.getProjectDirectory();
67+
GradleRunConfigurationAttributes configurationAttributes = getRunConfigurationAttributes(rootDir,
68+
workingDir, Arrays.asList(task.split("\\s+")));
69+
70+
// create/reuse a launch configuration for the given attributes
71+
ILaunchConfiguration launchConfig = CorePlugin.gradleLaunchConfigurationManager()
72+
.getOrCreateRunConfiguration(configurationAttributes);
73+
74+
DebugUITools.launch(launchConfig, ILaunchManager.RUN_MODE);
75+
} catch (Exception e) {
76+
throw new ExecutionException("Failed to execute Maven Goal command", e);
77+
}
78+
}
79+
throw new ExecutionException("Maven Goal Execution command is invalid");
80+
}
81+
82+
private static GradleRunConfigurationAttributes getRunConfigurationAttributes(File rootDir, File workingDir,
83+
List<String> tasks) {
84+
BuildConfiguration buildConfig = CorePlugin.configurationManager().loadBuildConfiguration(rootDir);
85+
return new GradleRunConfigurationAttributes(tasks, projectDirectoryExpression(workingDir),
86+
buildConfig.getGradleDistribution().toString(),
87+
gradleUserHomeExpression(buildConfig.getGradleUserHome()),
88+
javaHomeExpression(buildConfig.getJavaHome()), buildConfig.getJvmArguments(),
89+
buildConfig.getArguments(), buildConfig.isShowExecutionsView(), buildConfig.isShowExecutionsView(),
90+
buildConfig.isOverrideWorkspaceSettings(), buildConfig.isOfflineMode(),
91+
buildConfig.isBuildScansEnabled());
92+
}
93+
94+
private static String projectDirectoryExpression(File rootProjectDir) {
95+
// return the directory as an expression if the project is part of the
96+
// workspace, otherwise
97+
// return the absolute path of the project directory available on the Eclipse
98+
// project model
99+
Optional<IProject> project = CorePlugin.workspaceOperations().findProjectByLocation(rootProjectDir);
100+
if (project.isPresent()) {
101+
return ExpressionUtils.encodeWorkspaceLocation(project.get());
102+
} else {
103+
return rootProjectDir.getAbsolutePath();
104+
}
105+
}
106+
107+
private static String gradleUserHomeExpression(File gradleUserHome) {
108+
return gradleUserHome == null ? "" : gradleUserHome.getAbsolutePath();
109+
}
110+
111+
private static String javaHomeExpression(File javaHome) {
112+
return javaHome == null ? "" : javaHome.getAbsolutePath();
113+
}
114+
}

eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/commands/ExecuteMavenGoalHandler.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
5252
try {
5353
String pomPath = (String) cmd.getArguments().get(0);
5454
String goal = (String) cmd.getArguments().get(1);
55-
System.out.println("Project: '%s', goal: '%s'".formatted(pomPath, goal));
5655

5756
IResource pomFile = LSPEclipseUtils.findResourceFor(Paths.get(pomPath).toUri());
5857
ILaunchConfiguration launchConfig = createLaunchConfiguration(pomFile.getParent(), goal);

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BuildCommandProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ public interface BuildCommandProvider {
1717

1818
Command executeMavenGoal(IJavaProject project, String goal);
1919

20+
Command executeGradleBuild(IJavaProject project, String command);
21+
2022
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/DefaultBuildCommandProvider.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,32 @@
2828
public class DefaultBuildCommandProvider implements BuildCommandProvider {
2929

3030
private static final String CMD_EXEC_MAVEN_GOAL = "sts.maven.goal";
31+
private static final String CMD_EXEC_GRADLE_BUILD = "sts.gradle.build";
3132

3233
private static final Object MAVEN_LOCK = new Object();
3334

3435
public DefaultBuildCommandProvider(SimpleLanguageServer server) {
36+
37+
// Execute Maven Goal
3538
server.onCommand(CMD_EXEC_MAVEN_GOAL, params -> {
3639
String pomPath = extractString(params.getArguments().get(0));
3740
String goal = extractString(params.getArguments().get(1));
3841
return CompletableFuture.runAsync(() -> {
3942
try {
40-
mavenRegenerateMetadata(Paths.get(pomPath), goal.trim().split("\\s+")).get();
43+
executeMaven(Paths.get(pomPath), goal.trim().split("\\s+")).get();
44+
} catch (Exception e) {
45+
throw new CompletionException(e);
46+
}
47+
});
48+
});
49+
50+
// Execute Gradle Build
51+
server.onCommand(CMD_EXEC_GRADLE_BUILD, params -> {
52+
String gradleBuildPath = extractString(params.getArguments().get(0));
53+
String command = extractString(params.getArguments().get(1));
54+
return CompletableFuture.runAsync(() -> {
55+
try {
56+
executeGradle(Paths.get(gradleBuildPath), command.trim().split("\\s+")).get();
4157
} catch (Exception e) {
4258
throw new CompletionException(e);
4359
}
@@ -54,11 +70,20 @@ public Command executeMavenGoal(IJavaProject project, String goal) {
5470
return cmd;
5571
}
5672

73+
@Override
74+
public Command executeGradleBuild(IJavaProject project, String command) {
75+
Command cmd = new Command();
76+
cmd.setCommand(CMD_EXEC_GRADLE_BUILD);
77+
cmd.setTitle("Execute Gradle Build");
78+
cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command));
79+
return cmd;
80+
}
81+
5782
private static String extractString(Object o) {
5883
return o instanceof JsonPrimitive ? ((JsonPrimitive) o).getAsString() : o.toString();
5984
}
6085

61-
private CompletableFuture<Void> mavenRegenerateMetadata(Path pom, String[] goal) {
86+
private CompletableFuture<Void> executeMaven(Path pom, String[] goal) {
6287
synchronized(MAVEN_LOCK) {
6388
String[] cmd = new String[1 + goal.length];
6489
Path projectPath = pom.getParent();
@@ -77,5 +102,20 @@ private CompletableFuture<Void> mavenRegenerateMetadata(Path pom, String[] goal)
77102
}
78103
}
79104

80-
105+
private CompletableFuture<Void> executeGradle(Path gradleBuildPath, String[] command) {
106+
String[] cmd = new String[1 + command.length];
107+
Path projectPath = gradleBuildPath.getParent();
108+
Path mvnw = projectPath.resolve(OS.isWindows() ? "gradlew.cmd" : "gradlew");
109+
cmd[0] = Files.isRegularFile(mvnw) ? mvnw.toFile().toString() : "gradle";
110+
System.arraycopy(command, 0, cmd, 1, command.length);
111+
try {
112+
return Runtime.getRuntime().exec(cmd, null, projectPath.toFile()).onExit().thenAccept(process -> {
113+
if (process.exitValue() != 0) {
114+
throw new CompletionException("Failed to execute Gradle build", new IllegalStateException("Errors running gradle command: %s".formatted(String.join(" ", cmd))));
115+
}
116+
});
117+
} catch (IOException e) {
118+
throw new CompletionException(e);
119+
}
120+
}
81121
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/VSCodeBuildCommandProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,13 @@ public Command executeMavenGoal(IJavaProject project, String goal) {
2727
return cmd;
2828
}
2929

30+
@Override
31+
public Command executeGradleBuild(IJavaProject project, String command) {
32+
Command cmd = new Command();
33+
cmd.setCommand("gradle.runBuild");
34+
cmd.setTitle("Execute Gradle Build");
35+
cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), command));
36+
return cmd;
37+
}
38+
3039
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import org.springframework.ide.vscode.commons.java.IJavaProject;
4040
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
4141
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
42-
import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild;
4342
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
4443
import org.springframework.ide.vscode.commons.rewrite.java.AddAnnotationOverMethod;
4544
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -185,12 +184,10 @@ private List<CodeLens> createCodeLenses(IJavaProject project, MethodDeclaration
185184
}
186185

187186
private Optional<CodeLens> createRefreshCodeLens(IJavaProject project, String title, Range range) {
188-
if (ProjectBuild.MAVEN_PROJECT_TYPE.equals(project.getProjectBuild().getType())) {
189-
Command refreshCmd = repositoryMetadataService.regenerateMetadataCommand(project);
187+
return repositoryMetadataService.regenerateMetadataCommand(project).map(refreshCmd -> {
190188
refreshCmd.setTitle(title);
191-
return Optional.of(new CodeLens(range, refreshCmd, null));
192-
}
193-
return Optional.empty();
189+
return new CodeLens(range, refreshCmd, null);
190+
});
194191
}
195192

196193
static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, DataRepositoryModule module, IDataRepositoryAotMethodMetadata methodMetadata) {

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,20 @@ public DataRepositoryAotMetadataService(FileObserver fileObserver, JavaProjectFi
137137
public Optional<DataRepositoryAotMetadata> getRepositoryMetadata(IJavaProject project, String repositoryType) {
138138
String metadataFilePath = repositoryType.replace('.', '/') + ".json";
139139

140-
return IClasspathUtil.getOutputFolders(project.getClasspath())
141-
.map(outputFolder -> outputFolder.getParentFile().toPath().resolve("spring-aot/main/resources/").resolve(metadataFilePath))
142-
.findFirst()
143-
.flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
140+
switch (project.getProjectBuild().getType()) {
141+
case ProjectBuild.MAVEN_PROJECT_TYPE:
142+
return IClasspathUtil.getOutputFolders(project.getClasspath())
143+
.map(outputFolder -> outputFolder.getParentFile().toPath().resolve("spring-aot/main/resources/").resolve(metadataFilePath))
144+
.findFirst()
145+
.flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
146+
case ProjectBuild.GRADLE_PROJECT_TYPE:
147+
return IClasspathUtil.getSourceFolders(project.getClasspath())
148+
.filter(f -> f.isDirectory() && "aotResources".equals(f.getName()))
149+
.findFirst()
150+
.map(f -> f.toPath().resolve(metadataFilePath))
151+
.flatMap(filePath -> metadataCache.computeIfAbsent(filePath, this::readMetadataFile));
152+
}
153+
return Optional.empty();
144154
}
145155

146156
private Optional<DataRepositoryAotMetadata> readMetadataFile(Path filePath) {
@@ -154,7 +164,7 @@ private Optional<DataRepositoryAotMetadata> readMetadataFile(Path filePath) {
154164
return Optional.empty();
155165
}
156166

157-
public Command regenerateMetadataCommand(IJavaProject jp) {
167+
Optional<Command> regenerateMetadataCommand(IJavaProject jp) {
158168
switch (jp.getProjectBuild().getType()) {
159169
case ProjectBuild.MAVEN_PROJECT_TYPE:
160170
List<String> goal = new ArrayList<>();
@@ -170,9 +180,24 @@ public Command regenerateMetadataCommand(IJavaProject jp) {
170180
goal.add("compile");
171181
}
172182
goal.add("org.springframework.boot:spring-boot-maven-plugin:process-aot");
173-
return buildCmds.executeMavenGoal(jp, String.join(" ", goal));
183+
return Optional.ofNullable(buildCmds.executeMavenGoal(jp, String.join(" ", goal)));
184+
// case ProjectBuild.GRADLE_PROJECT_TYPE:
185+
// List<String> command = new ArrayList<>();
186+
// if (!IClasspathUtil.getOutputFolders(jp.getClasspath()).map(f -> f.toPath()).filter(Files::isDirectory).flatMap(d -> {
187+
// try {
188+
// return Files.walk(d);
189+
// } catch (IOException e) {
190+
// return Stream.empty();
191+
// }
192+
// }).anyMatch(f -> Files.isRegularFile(f) && f.getFileName().toString().endsWith(".class"))) {
193+
// // Check if source is compiled by checking that all output folders exist
194+
// // If not compiled then add `build` task
195+
// command.add("build");
196+
// }
197+
// command.add("processAot");
198+
// return Optional.ofNullable(buildCmds.executeGradleBuild(jp, String.join(" ", command)));
174199
}
175-
return null;
200+
return Optional.empty();
176201
}
177202

178203
public void addListener(Consumer<List<URI>> listener) {

0 commit comments

Comments
 (0)