From ad6a65873a56170ce8400bf9cc0adee9cc2f135b Mon Sep 17 00:00:00 2001 From: Alexander Bubenchikov Date: Tue, 17 Sep 2024 13:05:20 +0200 Subject: [PATCH] [maven] [IDEA-101997] replacing classpath in junit tests to filtered jar GitOrigin-RevId: da2e645e39f5083f3f25a0351699f5798cfb81d8 --- .../impl/MavenFilteredJarConfiguration.java | 3 + .../maven/execution/MavenJUnitPatcher.java | 67 ++++++++++++++- .../compilation/FilteredJarConfigGenerator.kt | 74 +--------------- .../idea/maven/utils/MavenFilteredJarUtils.kt | 84 +++++++++++++++++++ 4 files changed, 154 insertions(+), 74 deletions(-) create mode 100644 plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenFilteredJarUtils.kt diff --git a/plugins/maven/jps-plugin/src/org/jetbrains/jps/maven/model/impl/MavenFilteredJarConfiguration.java b/plugins/maven/jps-plugin/src/org/jetbrains/jps/maven/model/impl/MavenFilteredJarConfiguration.java index d8bab17449e3..d86e903dea7e 100644 --- a/plugins/maven/jps-plugin/src/org/jetbrains/jps/maven/model/impl/MavenFilteredJarConfiguration.java +++ b/plugins/maven/jps-plugin/src/org/jetbrains/jps/maven/model/impl/MavenFilteredJarConfiguration.java @@ -30,4 +30,7 @@ public class MavenFilteredJarConfiguration { @Tag("jarOutput") public @NotNull String jarOutput; + + @Tag("name") + public @NotNull String name; } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java index 380998478f6f..c52c601f8df3 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/execution/MavenJUnitPatcher.java @@ -9,7 +9,10 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.CompilerModuleExtension; import com.intellij.openapi.util.PropertiesUtil; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; import one.util.streamex.StreamEx; import org.jdom.Element; import org.jetbrains.annotations.NotNull; @@ -23,7 +26,9 @@ import org.jetbrains.idea.maven.project.MavenProject; import org.jetbrains.idea.maven.project.MavenProjectSettings; import org.jetbrains.idea.maven.project.MavenProjectsManager; import org.jetbrains.idea.maven.project.MavenTestRunningSettings; +import org.jetbrains.idea.maven.utils.MavenFilteredJarUtils; import org.jetbrains.idea.maven.utils.MavenJDOMUtil; +import org.jetbrains.jps.maven.model.impl.MavenFilteredJarConfiguration; import java.io.File; import java.io.IOException; @@ -54,15 +59,71 @@ public final class MavenJUnitPatcher extends JUnitPatcher { public void patchJavaParameters(@Nullable Module module, JavaParameters javaParameters) { if (module == null) return; - MavenProject mavenProject = MavenProjectsManager.getInstance(module.getProject()).findProject(module); + MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(module.getProject()); + MavenProject mavenProject = projectsManager.findProject(module); if (mavenProject == null) return; UnaryOperator runtimeProperties = getDynamicConfigurationProperties(module, mavenProject, javaParameters); configureFromPlugin(module, javaParameters, mavenProject, runtimeProperties, "maven-surefire-plugin", "surefire"); configureFromPlugin(module, javaParameters, mavenProject, runtimeProperties, "maven-failsafe-plugin", "failsafe"); + replaceFilteredJarDirectories(projectsManager, module, javaParameters, mavenProject); } + private static void replaceFilteredJarDirectories(MavenProjectsManager projectsManager, + @NotNull Module module, + JavaParameters parameters, + MavenProject project) { + if (!Registry.is("maven.build.additional.jars")) return; + //todo: We do dependency traversing every time, we need another structure in project tree for this to retrieve this data in a fast way + Set visited = new HashSet<>(); + ArrayDeque queue = new ArrayDeque<>(project.getDependencies()); + List toReplace = new ArrayList<>(); + while (!queue.isEmpty()) { + var dependency = queue.poll(); + var depProject = projectsManager.findProject(dependency); + if (depProject == null) continue; + if (!visited.add(dependency)) continue; + MavenFilteredJarConfiguration jarConfiguration = + findFilteredJarConfig(projectsManager, depProject, dependency.getClassifier()); + if (jarConfiguration != null) { + LOG.debug( + "found additional jar configuration for " + dependency + ", classpath will be replaced in tests for module " + module.getName()); + toReplace.add(jarConfiguration); + } + queue.addAll(depProject.getDependencies()); + } + + if (toReplace.isEmpty()) return; + //do not expect a lot of MavenFilteredJarConfiguration here, O(n^2) should be fine + String[] paths = ArrayUtil.toStringArray(parameters.getClassPath().getPathList()); + boolean replaced = false; + for (int i = 0; i < paths.length; i++) { + String path = paths[i]; + MavenFilteredJarConfiguration config = ContainerUtil.find(toReplace, c -> FileUtil.pathsEqual(path, c.originalOutput)); + if (config != null) { + paths[i] = config.jarOutput; + replaced = true; + } + } + if (!replaced) { + LOG.warn( + "expected to replace " + toReplace.size() + " dependencies in running module " + module.getName() + ", but replaced 0"); + } + else { + parameters.getClassPath().clear(); + parameters.getClassPath().addAll(Arrays.asList(paths)); + } + } + + private static @Nullable MavenFilteredJarConfiguration findFilteredJarConfig(MavenProjectsManager projectsManager, + MavenProject mavenProject, String classifier) { + List<@NotNull MavenFilteredJarConfiguration> configurations = + MavenFilteredJarUtils.getAllFilteredConfigurations(projectsManager, mavenProject); + return ContainerUtil.find(configurations, c -> StringUtil.equals(c.classifier, classifier)); + } + + private static void configureFromPlugin(@NotNull Module module, JavaParameters javaParameters, MavenProject mavenProject, @@ -155,7 +216,7 @@ public final class MavenJUnitPatcher extends JUnitPatcher { MavenProjectsManager mavenProjectsManager = MavenProjectsManager.getInstance(module.getProject()); if (scopeExclude != null || !excludes.isEmpty()) { for (MavenArtifact dependency : mavenProject.getDependencies()) { - if (scopeExclude!=null && SCOPE_FILTER.getOrDefault(scopeExclude, Collections.emptyList()).contains(dependency.getScope()) || + if (scopeExclude != null && SCOPE_FILTER.getOrDefault(scopeExclude, Collections.emptyList()).contains(dependency.getScope()) || excludes.contains(dependency.getGroupId() + ":" + dependency.getArtifactId())) { File file = dependency.getFile(); javaParameters.getClassPath().remove(file.getAbsolutePath()); @@ -281,7 +342,7 @@ public final class MavenJUnitPatcher extends JUnitPatcher { while (matcher.find()) { String finding = matcher.group(); final String propertyValue = vmParameters.getPropertyValue(finding.substring(2, finding.length() - 1)); - if(propertyValue == null) continue; + if (propertyValue == null) continue; toReplace.put(finding, propertyValue); } for (Map.Entry entry : toReplace.entrySet()) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/compilation/FilteredJarConfigGenerator.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/compilation/FilteredJarConfigGenerator.kt index dbf735f07e8b..9d86af5a2f8f 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/compilation/FilteredJarConfigGenerator.kt +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/compilation/FilteredJarConfigGenerator.kt @@ -1,17 +1,13 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.maven.project.compilation -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.util.registry.Registry -import com.intellij.util.text.nullize -import org.jdom.Element import org.jetbrains.annotations.ApiStatus import org.jetbrains.idea.maven.project.MavenProject import org.jetbrains.idea.maven.project.MavenProjectsManager import org.jetbrains.idea.maven.server.RemotePathTransformerFactory -import org.jetbrains.idea.maven.utils.MavenJDOMUtil.findChildrenValuesByPath -import org.jetbrains.jps.maven.model.impl.MavenFilteredJarConfiguration +import org.jetbrains.idea.maven.utils.MavenFilteredJarUtils import org.jetbrains.jps.maven.model.impl.MavenProjectConfiguration @ApiStatus.Internal @@ -27,72 +23,8 @@ class FilteredJarConfigGenerator( return } if ("pom".equals(mavenProject.packaging)) return; - - GOALS.forEach { g, _ -> - mavenProject.getPluginGoalConfiguration("org.apache.maven.plugins", "maven-jar-plugin", g)?.let { - addConfiguration(g, it) - } + MavenFilteredJarUtils.getAllFilteredConfigurations(mavenProjectsManager, mavenProject).forEach { jarConfig -> + config.jarsConfiguration[jarConfig.name] = jarConfig } } - - - private fun addConfiguration(goal: String, element: Element) { - val includes = findChildrenValuesByPath(element, "includes", "include").toMutableList() - val excludes = findChildrenValuesByPath(element, "excludes", "exclude").toMutableList() - val classifier = element.getChildTextTrim("classifier") ?: GOALS[goal] ?: "" - val excludeDefaults: Boolean - if ("false".equals(element.getChildTextTrim("addDefaultExcludes"), true)) { - excludeDefaults = false - } - else { - excludeDefaults = true - } - - if (excludeDefaults) { - DEFAULTEXCLUDES.forEach { _, v -> v.forEach(excludes::add) } - } - doAddConfiguration(goal == "test-jar", classifier, excludes, includes) - - } - - private fun doAddConfiguration(tests: Boolean, classifier: String, excludes: MutableList, includes: MutableList) { - val module = mavenProjectsManager.findModule(mavenProject) ?: return - val configuration = MavenFilteredJarConfiguration() - - configuration.classifier = classifier - configuration.excludes = excludes.toSet() - configuration.includes = includes.toSet() - configuration.moduleName = module.name - val name = mavenProject.mavenId.toString() + (classifier.nullize(true)?.map { "-$it" } ?: "") - configuration.isTest = tests - configuration.originalOutput = if (tests) mavenProject.testOutputDirectory else mavenProject.outputDirectory; - configuration.jarOutput = configuration.originalOutput + "-jar-" + classifier - config.jarsConfiguration[name] = configuration - } - - - companion object { - private val LOG = Logger.getInstance(FilteredJarConfigGenerator::class.java) - private val GOALS = mapOf("jar" to "", "test-jar" to "tests") - - //https://codehaus-plexus.github.io/plexus-utils/apidocs/org/codehaus/plexus/util/AbstractScanner.html#DEFAULTEXCLUDES - private val DEFAULTEXCLUDES = mapOf>( - "Misc" to arrayOf(" **/*~", "**/#*#", "**/.#*", "**/%*%", "*/._*"), - "CVS" to arrayOf("*/CVS", "**/CVS/**", "**/.cvsignore"), - "RCS" to arrayOf("**/RCS", "**/RCS/**"), - "SCCS" to arrayOf("**/SCCS", "**/SCCS/**"), - "VSSercer" to arrayOf(" **/vssver.scc"), - "MKS" to arrayOf("**/project.pj"), - "SVN" to arrayOf("**/.svn", "**/.svn/**"), - "GNU" to arrayOf("**/.arch-ids", "**/.arch-ids/**"), - "Bazaar" to arrayOf("**/.bzr", "**/.bzr/**"), - "SurroundSCM" to arrayOf("**/.MySCMServerInfo"), - "Mac" to arrayOf("**/.DS_Store"), - "Serena Dimension" to arrayOf("**/.metadata", "**/.metadata/**"), - "Mercurial" to arrayOf("**/.hg", "**/.hg/**"), - "Git" to arrayOf("**/.git", "**/.git/**", "**/.gitignore"), - "Bitkeeper" to arrayOf("**/BitKeeper", "**/BitKeeper/**", "**/ChangeSet", "**/ChangeSet/**"), - "Darcs" to arrayOf("**/_darcs", "**/_darcs/**", "**/.darcsrepo", "**/.darcsrepo/****/-darcs-backup*", "**/.darcs-temp-mail"), - ) - } } \ No newline at end of file diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenFilteredJarUtils.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenFilteredJarUtils.kt new file mode 100644 index 000000000000..dc564a7b6f3f --- /dev/null +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenFilteredJarUtils.kt @@ -0,0 +1,84 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.maven.utils + +import com.intellij.util.text.nullize +import org.jdom.Element +import org.jetbrains.idea.maven.project.MavenProject +import org.jetbrains.idea.maven.project.MavenProjectsManager +import org.jetbrains.idea.maven.utils.MavenJDOMUtil.findChildrenValuesByPath +import org.jetbrains.jps.maven.model.impl.MavenFilteredJarConfiguration + +class MavenFilteredJarUtils { + + companion object { + @JvmStatic + fun getAllFilteredConfigurations(mavenProjectsManager: MavenProjectsManager, mavenProject: MavenProject): List { + if ("pom".equals(mavenProject.packaging)) return emptyList() + + val result = ArrayList() + for (e in GOALS) { + val element = mavenProject.getPluginGoalConfiguration("org.apache.maven.plugins", "maven-jar-plugin", e.key) ?: continue + loadConfiguration(mavenProjectsManager, mavenProject, element, e.key)?.also { + result.add(it) + } + } + return result + } + + private fun loadConfiguration(mavenProjectsManager: MavenProjectsManager, mavenProject: MavenProject, element: Element, goal: String): MavenFilteredJarConfiguration? { + val includes = findChildrenValuesByPath(element, "includes", "include").toMutableList() + val excludes = findChildrenValuesByPath(element, "excludes", "exclude").toMutableList() + if (excludes.isEmpty() && includes.isEmpty()) return null //no configurations if jar is not filtered + val classifier = element.getChildTextTrim("classifier") ?: GOALS[goal] ?: "" + val excludeDefaults: Boolean + if ("false".equals(element.getChildTextTrim("addDefaultExcludes"), true)) { + excludeDefaults = false + } + else { + excludeDefaults = true + } + + if (excludeDefaults) { + DEFAULTEXCLUDES.forEach { _, v -> v.forEach(excludes::add) } + } + + val module = mavenProjectsManager.findModule(mavenProject) ?: return null + val configuration = MavenFilteredJarConfiguration() + val tests = goal == "test-jar" + + configuration.classifier = classifier + configuration.excludes = excludes.toSet() + configuration.includes = includes.toSet() + configuration.moduleName = module.name + configuration.isTest = tests + configuration.originalOutput = if (tests) mavenProject.testOutputDirectory else mavenProject.outputDirectory; + configuration.jarOutput = configuration.originalOutput + "-jar-" + classifier + configuration.name = mavenProject.mavenId.toString() + (classifier.nullize(true)?.map { "-$it" } ?: "") + return configuration + } + + private val GOALS = mapOf("jar" to "", "test-jar" to "tests") + + //https://codehaus-plexus.github.io/plexus-utils/apidocs/org/codehaus/plexus/util/AbstractScanner.html#DEFAULTEXCLUDES + private val DEFAULTEXCLUDES = mapOf>( + "Misc" to arrayOf(" **/*~", "**/#*#", "**/.#*", "**/%*%", "*/._*"), + "CVS" to arrayOf("*/CVS", "**/CVS/**", "**/.cvsignore"), + "RCS" to arrayOf("**/RCS", "**/RCS/**"), + "SCCS" to arrayOf("**/SCCS", "**/SCCS/**"), + "VSSercer" to arrayOf(" **/vssver.scc"), + "MKS" to arrayOf("**/project.pj"), + "SVN" to arrayOf("**/.svn", "**/.svn/**"), + "GNU" to arrayOf("**/.arch-ids", "**/.arch-ids/**"), + "Bazaar" to arrayOf("**/.bzr", "**/.bzr/**"), + "SurroundSCM" to arrayOf("**/.MySCMServerInfo"), + "Mac" to arrayOf("**/.DS_Store"), + "Serena Dimension" to arrayOf("**/.metadata", "**/.metadata/**"), + "Mercurial" to arrayOf("**/.hg", "**/.hg/**"), + "Git" to arrayOf("**/.git", "**/.git/**", "**/.gitignore"), + "Bitkeeper" to arrayOf("**/BitKeeper", "**/BitKeeper/**", "**/ChangeSet", "**/ChangeSet/**"), + "Darcs" to arrayOf("**/_darcs", "**/_darcs/**", "**/.darcsrepo", "**/.darcsrepo/****/-darcs-backup*", "**/.darcs-temp-mail"), + ) + } + + +} \ No newline at end of file