Account for modulith dependency to be a local project

This commit is contained in:
aboyko
2023-07-12 20:52:01 -04:00
parent eef3b5c35d
commit 1d0c488cf8
6 changed files with 133 additions and 116 deletions

View File

@@ -1,12 +1,12 @@
name: Eclipse LS Extensions Snapshot Build
# configure manual trigger
on:
workflow_dispatch:
#on:
# push:
# branches:
# - 'main'
# workflow_dispatch:
on:
push:
branches:
- 'main'
jobs:

View File

@@ -19,6 +19,8 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
public class SpringProjectUtil {
@@ -124,6 +126,34 @@ public class SpringProjectUtil {
return null;
}
public static boolean hasDependencyStartingWith(IJavaProject jp, String dependency, Predicate<CPE> filter) {
try {
for (CPE cpe : jp.getClasspath().getClasspathEntries()) {
if (filter == null || filter.test(cpe)) {
if (Classpath.ENTRY_KIND_BINARY.equals(cpe.getKind())) {
String name = new File(cpe.getPath()).getName();
if (name.endsWith(".jar") && name.startsWith(dependency)) {
return true;
}
} else if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind()) && !cpe.isOwn()) {
if (cpe.getExtra() != null && cpe.getExtra().containsKey("project")) {
if (new File(cpe.getExtra().get("project")).getName().startsWith(dependency)) {
return true;
}
} else {
if (new File(cpe.getPath()).getName().startsWith(dependency)) {
return true;
}
}
}
}
}
} catch (Exception e) {
log.error("", e);
}
return false;
}
public static Version getSpringBootVersion(IJavaProject jp) {
try {
for (File f : IClasspathUtil.getBinaryRoots(jp.getClasspath(), (cpe) -> !cpe.isSystem())) {

View File

@@ -14,6 +14,8 @@ import java.io.File;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class Classpath {
@@ -60,14 +62,8 @@ public class Classpath {
private boolean isOwn = false;
private boolean isTest = false;
private boolean isJavaContent = false;
public String getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(String outputFolder) {
this.outputFolder = outputFolder;
}
private Map<String, String> extra;
public CPE() {}
@@ -77,6 +73,29 @@ public class Classpath {
setPath(path);
}
public CPE(String kind, String path, Map<String, String> extra) {
super();
this.kind = kind;
this.extra = extra;
setPath(path);
}
public Map<String, String> getExtra() {
return extra;
}
public void setExtra(Map<String, String> extra) {
this.extra = extra;
}
public String getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(String outputFolder) {
this.outputFolder = outputFolder;
}
public String getKind() {
return kind;
}
@@ -119,6 +138,12 @@ public class Classpath {
return cpe;
}
public static CPE source(File sourceFolder, File outputFolder, Map<String, String> extra) {
CPE cpe = new CPE(ENTRY_KIND_SOURCE, sourceFolder.getAbsolutePath(), extra);
cpe.setOutputFolder(outputFolder.getAbsolutePath());
return cpe;
}
public boolean isSystem() {
return isSystem;
}
@@ -155,23 +180,14 @@ public class Classpath {
public String toString() {
return "CPE [kind=" + kind + ", path=" + path + ", outputFolder=" + outputFolder + ", sourceContainerUrl="
+ sourceContainerUrl + ", javadocContainerUrl=" + javadocContainerUrl + ", isSystem=" + isSystem
+ ", isOwn=" + isOwn + ", isTest=" + isTest + ", isJavaContent=" + isJavaContent + "]";
+ ", isOwn=" + isOwn + ", isTest=" + isTest + ", isJavaContent=" + isJavaContent + ", extra="
+ extra + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (isJavaContent ? 1231 : 1237);
result = prime * result + (isOwn ? 1231 : 1237);
result = prime * result + (isSystem ? 1231 : 1237);
result = prime * result + (isTest ? 1231 : 1237);
result = prime * result + ((javadocContainerUrl == null) ? 0 : javadocContainerUrl.hashCode());
result = prime * result + ((kind == null) ? 0 : kind.hashCode());
result = prime * result + ((outputFolder == null) ? 0 : outputFolder.hashCode());
result = prime * result + ((path == null) ? 0 : path.hashCode());
result = prime * result + ((sourceContainerUrl == null) ? 0 : sourceContainerUrl.hashCode());
return result;
return Objects.hash(extra, isJavaContent, isOwn, isSystem, isTest, javadocContainerUrl, kind, outputFolder,
path, sourceContainerUrl);
}
@Override
@@ -183,40 +199,11 @@ public class Classpath {
if (getClass() != obj.getClass())
return false;
CPE other = (CPE) obj;
if (isJavaContent != other.isJavaContent)
return false;
if (isOwn != other.isOwn)
return false;
if (isSystem != other.isSystem)
return false;
if (isTest != other.isTest)
return false;
if (javadocContainerUrl == null) {
if (other.javadocContainerUrl != null)
return false;
} else if (!javadocContainerUrl.equals(other.javadocContainerUrl))
return false;
if (kind == null) {
if (other.kind != null)
return false;
} else if (!kind.equals(other.kind))
return false;
if (outputFolder == null) {
if (other.outputFolder != null)
return false;
} else if (!outputFolder.equals(other.outputFolder))
return false;
if (path == null) {
if (other.path != null)
return false;
} else if (!path.equals(other.path))
return false;
if (sourceContainerUrl == null) {
if (other.sourceContainerUrl != null)
return false;
} else if (!sourceContainerUrl.equals(other.sourceContainerUrl))
return false;
return true;
return Objects.equals(extra, other.extra) && isJavaContent == other.isJavaContent && isOwn == other.isOwn
&& isSystem == other.isSystem && isTest == other.isTest
&& Objects.equals(javadocContainerUrl, other.javadocContainerUrl)
&& Objects.equals(kind, other.kind) && Objects.equals(outputFolder, other.outputFolder)
&& Objects.equals(path, other.path) && Objects.equals(sourceContainerUrl, other.sourceContainerUrl);
}
}

View File

@@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -169,7 +170,7 @@ public class ClasspathUtil {
} else {
absoluteOutFolder = resolveWorkspacePath(javaProject.getOutputLocation());
}
return CPE.source(absoluteSourcePath.toFile(), absoluteOutFolder.toFile());
return CPE.source(absoluteSourcePath.toFile(), absoluteOutFolder.toFile(), Map.of("project", javaProject.getProject().getLocation().toString()));
}
return null;
}

View File

@@ -27,8 +27,6 @@ import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
import org.springframework.ide.vscode.boot.modulith.AppModules;
import org.springframework.ide.vscode.boot.modulith.ModulithService;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.rewrite.config.MarkerVisitorContext;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
@@ -101,8 +99,7 @@ public class ModulithTypeReferenceViolation implements RecipeCodeActionDescripto
@Override
public boolean isApplicable(IJavaProject project) {
Version v = SpringProjectUtil.getDependencyVersion(project, "spring-modulith-core");
return v != null;
return ModulithService.isModulithDependentProject(project);
}
@Override

View File

@@ -44,7 +44,6 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaProjectReconcil
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory.Toggle.Option;
@@ -103,8 +102,7 @@ public class ModulithService {
@Override
public void created(IJavaProject project) {
Version v = SpringProjectUtil.getDependencyVersion(project, "spring-modulith-core");
if (v != null) {
if (isModulithDependentProject(project)) {
if (anyClassFilesPresent(project)) {
requestMetadata(project);
} else {
@@ -115,8 +113,7 @@ public class ModulithService {
@Override
public void changed(IJavaProject project) {
Version v = SpringProjectUtil.getDependencyVersion(project, "spring-modulith-core");
if (v == null) {
if (!isModulithDependentProject(project)) {
removeFromCache(project);
} else if (anyClassFilesPresent(project)) {
requestMetadata(project);
@@ -134,7 +131,7 @@ public class ModulithService {
server.onCommand(CMD_LIST_MODULITH_PROJECTS, params -> {
return CompletableFuture.completedFuture(projectFinder.all()
.stream()
.filter(p -> SpringProjectUtil.getDependencyVersion(p, "spring-modulith-core") != null)
.filter(ModulithService::isModulithDependentProject)
.collect(Collectors.toMap(p -> p.getElementName(), p -> p.getLocationUri().toASCIIString()))
);
});
@@ -155,8 +152,7 @@ public class ModulithService {
}
private CompletableFuture<Boolean> refreshMetadata(IJavaProject project) {
Version v = SpringProjectUtil.getDependencyVersion(project, "spring-modulith-core");
if (v == null) {
if (!isModulithDependentProject(project)) {
server.getClient().showMessage(new MessageParams(MessageType.Error, "Project '" + project.getElementName() + "' does not depend on spring-modulith."));
return CompletableFuture.completedFuture(false);
}
@@ -223,41 +219,37 @@ public class ModulithService {
}
private CompletableFuture<AppModules> loadModulesMetadata(IJavaProject project) {
Version v = SpringProjectUtil.getDependencyVersion(project, "spring-modulith-core");
if (v != null) {
log.info("Loading Modulith metadata for project '" + project.getElementName() + "'...");
return findRootPackages(project).thenComposeAsync(packages -> {
if (!packages.isEmpty()) {
try {
String javaCmd = ProcessHandle.current().info().command().orElseThrow();
String classpathStr = project.getClasspath().getClasspathEntries().stream().map(cpe -> {
if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind())) {
return cpe.getOutputFolder();
} else {
return cpe.getPath();
}
}).collect(Collectors.joining(System.getProperty("path.separator")));
List<AppModule> allAppModules = new ArrayList<>();
CompletableFuture<?>[] aggregateFuture = packages
.stream()
.map(pkg -> computeAppModules(project.getElementName(), javaCmd, classpathStr, pkg).thenAccept(allAppModules::addAll))
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(aggregateFuture).thenApply(r -> new AppModules(allAppModules));
} catch (Exception e) {
log.error("", e);
log.info("Loading Modulith metadata for project '" + project.getElementName() + "'...");
return findRootPackages(project).thenComposeAsync(packages -> {
if (!packages.isEmpty()) {
try {
String javaCmd = ProcessHandle.current().info().command().orElseThrow();
String classpathStr = project.getClasspath().getClasspathEntries().stream().map(cpe -> {
if (Classpath.ENTRY_KIND_SOURCE.equals(cpe.getKind())) {
return cpe.getOutputFolder();
} else {
return cpe.getPath();
}
}
return CompletableFuture.completedFuture(null);
});
}
return CompletableFuture.completedFuture(null);
}).collect(Collectors.joining(System.getProperty("path.separator")));
List<AppModule> allAppModules = new ArrayList<>();
CompletableFuture<?>[] aggregateFuture = packages.stream()
.map(pkg -> computeAppModules(project.getElementName(), javaCmd, classpathStr, pkg)
.thenAccept(allAppModules::addAll))
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(aggregateFuture).thenApply(r -> new AppModules(allAppModules));
} catch (Exception e) {
log.error("", e);
}
}
return CompletableFuture.completedFuture(null);
});
}
private CompletableFuture<List<AppModule>> computeAppModules(String projectName, String javaCmd,
String cp, String pkg) {
try {
File outputFile = File.createTempFile(projectName + "-" + pkg, "json");
return Runtime.getRuntime()
Process process = Runtime.getRuntime()
.exec(new String[] {
javaCmd,
"-cp",
@@ -265,22 +257,28 @@ public class ModulithService {
"org.springframework.modulith.core.util.ApplicationModulesExporter",
pkg,
outputFile.toString()
})
.onExit().thenApply(process -> {
if (process.exitValue() == 0) {
try {
log.info("Updating Modulith metadata for project '" + projectName + "'");
JsonObject json = JsonParser.parseReader(new FileReader(outputFile)).getAsJsonObject();
log.info("Modulith metadata: " + json);
return loadAppModules(json);
} catch (Exception e) {
log.error("", e);
}
} else {
log.error("Failed to generate modulith metadata for project '" + projectName + "'. Modulith Exporter process exited with code " + process.exitValue());
}
return Collections.emptyList();
});
StringBuilder builder = new StringBuilder();
String line = null;
while ((line = process.errorReader().readLine()) != null) {
builder.append(line);
builder.append(System.getProperty("line.separator"));
}
return process.onExit().thenApply(p -> {
if (p.exitValue() == 0) {
try {
log.info("Updating Modulith metadata for project '" + projectName + "'");
JsonObject json = JsonParser.parseReader(new FileReader(outputFile)).getAsJsonObject();
log.info("Modulith metadata: " + json);
return loadAppModules(json);
} catch (Exception e) {
log.error("", e);
}
} else {
log.error("Failed to generate modulith metadata for project '" + projectName + "'. Modulith Exporter process exited with code " + process.exitValue() + "\n" + builder.toString());
}
return Collections.emptyList();
});
} catch (IOException e) {
log.error("", e);
}
@@ -356,4 +354,8 @@ public class ModulithService {
return Stream.empty();
}
}
public static boolean isModulithDependentProject(IJavaProject project) {
return SpringProjectUtil.hasDependencyStartingWith(project, "spring-modulith-core", cpe -> !cpe.isSystem() && !cpe.isTest() && !cpe.isOwn());
}
}