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) {