Skip to content

Commit 4c36785

Browse files
committed
Basic Gradle support for Spring Data AOT
Signed-off-by: BoykoAlex <[email protected]>
1 parent c65c5f0 commit 4c36785

File tree

9 files changed

+167
-58
lines changed

9 files changed

+167
-58
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0",
2727
com.google.gson,
2828
org.eclipse.m2e.launching,
2929
org.eclipse.m2e.core,
30-
org.eclipse.buildship.core
30+
org.eclipse.buildship.core,
31+
org.gradle.toolingapi
3132
Bundle-RequiredExecutionEnvironment: JavaSE-21
3233
Bundle-ActivationPolicy: lazy
3334
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)"

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

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
import java.util.Arrays;
1616
import java.util.List;
1717

18+
import org.gradle.tooling.model.eclipse.EclipseProject;
19+
import org.eclipse.buildship.core.GradleCore;
1820
import org.eclipse.buildship.core.internal.CorePlugin;
1921
import org.eclipse.buildship.core.internal.configuration.BuildConfiguration;
2022
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
23+
import org.eclipse.buildship.core.internal.util.gradle.HierarchicalElementUtils;
2124
import org.eclipse.buildship.core.internal.util.variable.ExpressionUtils;
2225
import org.eclipse.core.commands.AbstractHandler;
2326
import org.eclipse.core.commands.ExecutionEvent;
2427
import org.eclipse.core.commands.ExecutionException;
2528
import org.eclipse.core.resources.IProject;
2629
import org.eclipse.core.resources.IResource;
30+
import org.eclipse.core.runtime.NullProgressMonitor;
2731
import org.eclipse.debug.core.ILaunchConfiguration;
2832
import org.eclipse.debug.core.ILaunchManager;
2933
import org.eclipse.debug.ui.DebugUITools;
@@ -39,19 +43,33 @@ public class ExecuteGradleTaskHandler extends AbstractHandler {
3943

4044
@Override
4145
public Object execute(ExecutionEvent event) throws ExecutionException {
42-
Command cmd = new Gson().fromJson(event.getParameter(LSPCommandHandler.LSP_COMMAND_PARAMETER_ID), Command.class);
46+
Command cmd = new Gson().fromJson(event.getParameter(LSPCommandHandler.LSP_COMMAND_PARAMETER_ID),
47+
Command.class);
4348
if (cmd != null) {
4449
try {
45-
String projectPath = (String) cmd.getArguments().get(0);
50+
String buildGradlePath = (String) cmd.getArguments().get(0);
4651
String task = (String) cmd.getArguments().get(1);
47-
System.out.println("Project: '%s', task: '%s'".formatted(projectPath, task));
4852

49-
IResource projectFolder = LSPEclipseUtils.findResourceFor(Paths.get(projectPath).toUri());
50-
File workingFolder = projectFolder.getLocation().toFile();
51-
GradleRunConfigurationAttributes configurationAttributes = getRunConfigurationAttributes(workingFolder, workingFolder, Arrays.asList(task.split("\\s+")));
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();
5264

53-
// create/reuse a launch configuration for the given attributes
54-
ILaunchConfiguration launchConfig = CorePlugin.gradleLaunchConfigurationManager().getOrCreateRunConfiguration(configurationAttributes);
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);
5573

5674
DebugUITools.launch(launchConfig, ILaunchManager.RUN_MODE);
5775
} catch (Exception e) {
@@ -61,38 +79,36 @@ public Object execute(ExecutionEvent event) throws ExecutionException {
6179
throw new ExecutionException("Maven Goal Execution command is invalid");
6280
}
6381

64-
private static GradleRunConfigurationAttributes getRunConfigurationAttributes(File rootDir, File workingDir, List<String> tasks) {
65-
BuildConfiguration buildConfig = CorePlugin.configurationManager().loadBuildConfiguration(rootDir);
66-
return new GradleRunConfigurationAttributes(tasks,
67-
projectDirectoryExpression(workingDir),
68-
buildConfig.getGradleDistribution().toString(),
69-
gradleUserHomeExpression(buildConfig.getGradleUserHome()),
70-
javaHomeExpression(buildConfig.getJavaHome()),
71-
buildConfig.getJvmArguments(),
72-
buildConfig.getArguments(),
73-
buildConfig.isShowExecutionsView(),
74-
buildConfig.isShowExecutionsView(),
75-
buildConfig.isOverrideWorkspaceSettings(),
76-
buildConfig.isOfflineMode(),
77-
buildConfig.isBuildScansEnabled());
78-
}
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+
}
7993

80-
private static String projectDirectoryExpression(File rootProjectDir) {
81-
// return the directory as an expression if the project is part of the workspace, otherwise
82-
// return the absolute path of the project directory available on the Eclipse project model
83-
Optional<IProject> project = CorePlugin.workspaceOperations().findProjectByLocation(rootProjectDir);
84-
if (project.isPresent()) {
85-
return ExpressionUtils.encodeWorkspaceLocation(project.get());
86-
} else {
87-
return rootProjectDir.getAbsolutePath();
88-
}
89-
}
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+
}
90106

91-
private static String gradleUserHomeExpression(File gradleUserHome) {
92-
return gradleUserHome == null ? "" : gradleUserHome.getAbsolutePath();
93-
}
107+
private static String gradleUserHomeExpression(File gradleUserHome) {
108+
return gradleUserHome == null ? "" : gradleUserHome.getAbsolutePath();
109+
}
94110

95-
private static String javaHomeExpression(File javaHome) {
96-
return javaHome == null ? "" : javaHome.getAbsolutePath();
97-
}
111+
private static String javaHomeExpression(File javaHome) {
112+
return javaHome == null ? "" : javaHome.getAbsolutePath();
113+
}
98114
}

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)