IDE support for TestJars: maven projects in the workspace only

This commit is contained in:
aboyko
2024-02-26 10:33:17 -05:00
parent 13c2c04997
commit 52dd4d2ff5
24 changed files with 828 additions and 176 deletions

View File

@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2024 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.ide.vscode.boot.app;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
@Configuration(proxyBeanMethods = false)
public class CommandsConfig {
@Bean WorkspaceBootExecutableProjects workspaceBootProjects(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) {
return new WorkspaceBootExecutableProjects(server, projectFinder, symbolIndex);
}
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright (c) 2024 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.ide.vscode.boot.java.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.commons.java.IGav;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
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.Classpath;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.BeansParams;
public class WorkspaceBootExecutableProjects {
public record ExecutableProject(String name, String uri, String gav, String mainClass, Collection<String> classpath) {}
final static String CMD = "sts/spring-boot/executableBootProjects";
private final static Logger log = LoggerFactory.getLogger(WorkspaceBootExecutableProjects.class);
final private JavaProjectFinder projectFinder;
final private SpringSymbolIndex symbolIndex;
public WorkspaceBootExecutableProjects(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) {
this.projectFinder = projectFinder;
this.symbolIndex = symbolIndex;
server.onCommand(CMD, params -> findExecutableProjects());
}
private CompletableFuture<Optional<ExecutableProject>> mapToExecProject(IJavaProject project) {
BeansParams params = new BeansParams();
params.setProjectName(project.getElementName());
return symbolIndex.beans(params).thenApply(beans -> {
List<Bean> bootAppBeans = beans.stream()
.filter(b -> Arrays.asList(b.getAnnotations()).contains(Annotations.BOOT_APP))
.limit(2)
.collect(Collectors.toList());
if (bootAppBeans.size() == 1) {
try {
Bean appBean = bootAppBeans.get(0);
Collection<String> classpath = project.getClasspath().getClasspathEntries().stream()
.filter(cpe -> !cpe.isTest() && !cpe.isSystem())
.map(cpe -> Classpath.isSource(cpe) ? cpe.getOutputFolder() : cpe.getPath())
.collect(Collectors.toSet());
IGav gav = project.getProjectBuild().getGav();
String gavStr = "%s:%s:%s".formatted(gav.getGroupId(), gav.getArtifactId(), gav.getVersion());
return Optional.of(new ExecutableProject(project.getElementName(), project.getLocationUri().toASCIIString(), gavStr, appBean.getType(), classpath));
} catch (Exception e) {
log.error("", e);
}
}
return Optional.empty();
});
}
private CompletableFuture<List<ExecutableProject>> findExecutableProjects() {
List<CompletableFuture<Optional<ExecutableProject>>> futures = projectFinder.all().stream()
.filter(p -> p.getProjectBuild().getGav() != null)
.filter(p -> SpringProjectUtil.isBootProject(p))
.map(this::mapToExecProject)
.collect(Collectors.toList());
List<ExecutableProject> executableProjects = Collections.synchronizedList(new ArrayList<>());
futures.forEach(f -> f.thenAccept(opt -> opt.ifPresent(executableProjects::add)));
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).thenApply(v -> executableProjects);
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018, 2023 Pivotal, Inc.
* Copyright (c) 2018, 2024 Pivotal, 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
@@ -385,7 +385,7 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService, Serv
}
private static IProjectBuild from(ProjectBuild projectBuild) {
return projectBuild == null ? null : IProjectBuild.create(projectBuild.getType(), projectBuild.getBuildFile() == null ? null : URI.create(projectBuild.getBuildFile()));
return projectBuild == null ? null : IProjectBuild.create(projectBuild.type(), projectBuild.buildFile() == null ? null : URI.create(projectBuild.buildFile()), projectBuild.gav());
}
}

View File

@@ -0,0 +1,119 @@
/*******************************************************************************
* Copyright (c) 2024 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.ide.vscode.boot.java.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects.ExecutableProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class WorkspaceBootExecutableProjectsTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private SpringSymbolIndex symbolIndex;
@BeforeEach
public void setup() throws Exception {
harness.intialize(null);
}
@SuppressWarnings("unchecked")
@Test
void singleProject() throws Exception {
MavenJavaProject project = ProjectsHarness.INSTANCE.mavenProject("test-spring-indexing");
// trigger project creation
projectFinder.find(new TextDocumentIdentifier(project.getProjectBuild().getBuildFile().toASCIIString())).get();
CompletableFuture<Void> initProject = symbolIndex.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
List<WorkspaceBootExecutableProjects.ExecutableProject> res = (List<WorkspaceBootExecutableProjects.ExecutableProject>) harness.getServer().getWorkspaceService().executeCommand(new ExecuteCommandParams(WorkspaceBootExecutableProjects.CMD, Collections.emptyList())).get();
assertNotNull(res);
assertEquals(1, res.size());
ExecutableProject execProject = res.get(0);
assertEquals("test-spring-indexing", execProject.name());
assertEquals("org.test.MainClass", execProject.mainClass());
assertEquals(project.getLocationUri().toASCIIString(), execProject.uri());
assertEquals("com.example:test-spring-indexing:0.0.1-SNAPSHOT", execProject.gav());
assertEquals(99, execProject.classpath().size());
assertTrue(execProject.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
assertFalse(execProject.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
}
@SuppressWarnings("unchecked")
@Test
void multipleProjects() throws Exception {
MavenJavaProject project1 = ProjectsHarness.INSTANCE.mavenProject("test-spring-indexing");
MavenJavaProject project2 = ProjectsHarness.INSTANCE.mavenProject("test-spring-data-symbols");
ProjectsHarness.INSTANCE.mavenProject("test-annotation-indexing-non-boot-project");
ProjectsHarness.INSTANCE.mavenProject("test-xml-validations");
// trigger project creation
projectFinder.find(new TextDocumentIdentifier(project1.getProjectBuild().getBuildFile().toASCIIString())).get();
projectFinder.find(new TextDocumentIdentifier(project2.getProjectBuild().getBuildFile().toASCIIString())).get();
CompletableFuture<Void> initProject = symbolIndex.waitOperation();
initProject.get(10, TimeUnit.SECONDS);
List<WorkspaceBootExecutableProjects.ExecutableProject> res = (List<WorkspaceBootExecutableProjects.ExecutableProject>) harness.getServer().getWorkspaceService().executeCommand(new ExecuteCommandParams(WorkspaceBootExecutableProjects.CMD, Collections.emptyList())).get();
assertNotNull(res);
assertEquals(2, res.size());
ExecutableProject execProject2 = res.get(0);
assertEquals("test-spring-data-symbols", execProject2.name());
assertEquals("org.test.Application", execProject2.mainClass());
assertEquals(project2.getLocationUri().toASCIIString(), execProject2.uri());
assertEquals("com.example:test-spring-data-symbols:0.0.1-SNAPSHOT", execProject2.gav());
assertEquals(44, execProject2.classpath().size());
assertTrue(execProject2.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
assertFalse(execProject2.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
ExecutableProject execProject1 = res.get(1);
assertEquals("test-spring-indexing", execProject1.name());
assertEquals("org.test.MainClass", execProject1.mainClass());
assertEquals(project1.getLocationUri().toASCIIString(), execProject1.uri());
assertEquals("com.example:test-spring-indexing:0.0.1-SNAPSHOT", execProject1.gav());
assertEquals(99, execProject1.classpath().size());
assertTrue(execProject1.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/classes")));
assertFalse(execProject1.classpath().stream().map(path -> Path.of(path)).anyMatch(p -> p.endsWith("target/test-classes")));
}
}