diff --git a/.github/workflows/eclipse-ls-extensions-build.yml b/.github/workflows/eclipse-ls-extensions-build.yml index 7a62a5180..2b95ea112 100644 --- a/.github/workflows/eclipse-ls-extensions-build.yml +++ b/.github/workflows/eclipse-ls-extensions-build.yml @@ -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: diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index 399c56aa2..2223dcd23 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -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 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())) { diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java index 40538dc77..a646c079a 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/Classpath.java @@ -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 extra; public CPE() {} @@ -77,6 +73,29 @@ public class Classpath { setPath(path); } + public CPE(String kind, String path, Map extra) { + super(); + this.kind = kind; + this.extra = extra; + setPath(path); + } + + public Map getExtra() { + return extra; + } + + public void setExtra(Map 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 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); } } diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathUtil.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathUtil.java index 080dae23b..b95b87fcf 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathUtil.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClasspathUtil.java @@ -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; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/ModulithTypeReferenceViolation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/ModulithTypeReferenceViolation.java index c32253a5a..34ecc447d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/ModulithTypeReferenceViolation.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/ModulithTypeReferenceViolation.java @@ -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 diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/modulith/ModulithService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/modulith/ModulithService.java index 067c26cde..b64ff72d4 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/modulith/ModulithService.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/modulith/ModulithService.java @@ -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 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 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 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 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> 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()); + } }