Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
class="org.springframework.tooling.ls.eclipse.commons.commands.ExecuteMavenGoalHandler"
commandId="maven.goal.custom">
</handler>
<handler
class="org.springframework.tooling.ls.eclipse.commons.commands.ExecuteGradleTaskHandler"
commandId="gradle.runBuild">
</handler>
</extension>
<extension
point="org.eclipse.ui.commands">
Expand Down Expand Up @@ -201,7 +205,23 @@
</command>
<command
id="maven.goal.custom"
name="Explain with AI">
name="Execute Maven Goal">
<commandParameter
id="org.eclipse.lsp4e.path.param"
name="Resource Path (unnecessary, only to make lsp4e happy)"
optional="true"
typeId="org.eclipse.lsp4e.pathParameterType">
</commandParameter>
<commandParameter
id="org.eclipse.lsp4e.command.param"
name="Command id (unnecessary, only to make lsp4e happy)"
optional="true"
typeId="org.eclipse.lsp4e.commandParameterType">
</commandParameter>
</command>
<command
id="gradle.runBuild"
name="Execute Gradle Build">
<commandParameter
id="org.eclipse.lsp4e.path.param"
name="Resource Path (unnecessary, only to make lsp4e happy)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.commons.commands;

import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

import org.gradle.tooling.model.eclipse.EclipseProject;
import org.eclipse.buildship.core.GradleCore;
import org.eclipse.buildship.core.internal.CorePlugin;
import org.eclipse.buildship.core.internal.configuration.BuildConfiguration;
import org.eclipse.buildship.core.internal.launch.GradleRunConfigurationAttributes;
import org.eclipse.buildship.core.internal.util.gradle.HierarchicalElementUtils;
import org.eclipse.buildship.core.internal.util.variable.ExpressionUtils;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.command.LSPCommandHandler;
import org.eclipse.lsp4j.Command;

import com.google.common.base.Optional;
import com.google.gson.Gson;

@SuppressWarnings("restriction")
public class ExecuteGradleTaskHandler extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
Command cmd = new Gson().fromJson(event.getParameter(LSPCommandHandler.LSP_COMMAND_PARAMETER_ID),
Command.class);
if (cmd != null) {
try {
String buildGradlePath = (String) cmd.getArguments().get(0);
String task = (String) cmd.getArguments().get(1);

IResource buildGradle = LSPEclipseUtils.findResourceFor(Paths.get(buildGradlePath).toUri());
IProject project = buildGradle.getProject();
EclipseProject gradleProject = GradleCore.getWorkspace().getBuild(project).map(build -> {
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<String> 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<IProject> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface BuildCommandProvider {

Command executeMavenGoal(IJavaProject project, String goal);

Command executeGradleBuild(IJavaProject project, String command);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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<Void> mavenRegenerateMetadata(Path pom, String[] goal) {
private CompletableFuture<Void> executeMaven(Path pom, String[] goal) {
synchronized(MAVEN_LOCK) {
String[] cmd = new String[1 + goal.length];
Path projectPath = pom.getParent();
Expand All @@ -77,5 +102,20 @@ private CompletableFuture<Void> mavenRegenerateMetadata(Path pom, String[] goal)
}
}


private CompletableFuture<Void> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -185,12 +184,10 @@ private List<CodeLens> createCodeLenses(IJavaProject project, MethodDeclaration
}

private Optional<CodeLens> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,20 @@ public DataRepositoryAotMetadataService(FileObserver fileObserver, JavaProjectFi
public Optional<DataRepositoryAotMetadata> 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<DataRepositoryAotMetadata> readMetadataFile(Path filePath) {
Expand All @@ -154,7 +164,7 @@ private Optional<DataRepositoryAotMetadata> readMetadataFile(Path filePath) {
return Optional.empty();
}

public Command regenerateMetadataCommand(IJavaProject jp) {
Optional<Command> regenerateMetadataCommand(IJavaProject jp) {
switch (jp.getProjectBuild().getType()) {
case ProjectBuild.MAVEN_PROJECT_TYPE:
List<String> goal = new ArrayList<>();
Expand All @@ -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<String> 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<List<URI>> listener) {
Expand Down