From 8c3d78652cae6546474d11fa29ff1be8cd4e41d1 Mon Sep 17 00:00:00 2001 From: Andrey Cherkasov Date: Tue, 14 Jul 2026 13:00:36 +0400 Subject: [PATCH] [kotlin] KTIJ-39542 Handle Maven pluginManagement ordering during Kotlin config Avoid reordering plugins across `build/plugins` and `pluginManagement` when configuring Kotlin in Maven projects. The previous logic assumed the reordered plugin would always be resolved from `build/plugins`, which caused a NoSuchElementException for root POMs that keep javac settings in `pluginManagement`. (cherry picked from commit 431b37eece386b8c85a7a04cfd2a5cc139a4c355) IJ-CR-213466 GitOrigin-RevId: 631849e34a1b0907ac680566bd925378447fae53 --- .../jetbrains/kotlin/idea/maven/PomFile.kt | 14 +- .../KotlinSourceRootDirsMavenTest.kt | 194 ++++++++++++++++++ 2 files changed, 206 insertions(+), 2 deletions(-) diff --git a/plugins/kotlin/maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt b/plugins/kotlin/maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt index edae14d9f6c7..c1544cd18e74 100644 --- a/plugins/kotlin/maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt +++ b/plugins/kotlin/maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt @@ -201,11 +201,13 @@ class PomFile private constructor(private val xmlFile: XmlFile, val domModel: Ma ?: domModel.build.pluginManagement.plugins.plugins.firstOrNull { it.matches(groupArtifact) } fun isPluginAfter(plugin: MavenDomPlugin, referencePlugin: MavenDomPlugin): Boolean { - require(plugin.parent === referencePlugin.parent) { "Plugins should be siblings" } require(plugin !== referencePlugin) val referenceElement = referencePlugin.xmlElement!! var e: PsiElement = plugin.xmlElement!! + // `pluginManagement/plugins` and `build/plugins` can both contain the same Maven plugin. + // In that case there is no shared sibling order to compare inside one XML container. + if (e.parent !== referenceElement.parent) return false while (e !== referenceElement) { val prev = e.prevSibling ?: return false @@ -216,13 +218,21 @@ class PomFile private constructor(private val xmlFile: XmlFile, val domModel: Ma } private fun ensurePluginAfter(plugin: MavenDomPlugin, referencePlugin: MavenDomPlugin): MavenDomPlugin { + // Only reorder plugins that already live in the same XML list. Moving a plugin across + // `pluginManagement` and `build/plugins` would change the POM structure instead of order. + if (plugin.xmlElement?.parent !== referencePlugin.xmlElement?.parent) { + return plugin + } + if (!isPluginAfter(plugin, referencePlugin)) { // rearrange val referenceElement = referencePlugin.xmlElement!! val newElement = referenceElement.parent.addAfter(plugin.xmlElement!!, referenceElement) plugin.xmlTag?.delete() - return domModel.build.plugins.plugins.single { it.xmlElement == newElement } + return (domModel.build.plugins.plugins + domModel.build.pluginManagement.plugins.plugins) + .singleOrNull { it.xmlElement == newElement } + ?: error("Failed to find plugin after reordering in ${xmlFile.name}") } return plugin diff --git a/plugins/kotlin/maven/tests/test/org/jetbrains/kotlin/idea/maven/configuration/KotlinSourceRootDirsMavenTest.kt b/plugins/kotlin/maven/tests/test/org/jetbrains/kotlin/idea/maven/configuration/KotlinSourceRootDirsMavenTest.kt index 3cc070c0763b..14b5d19b8724 100644 --- a/plugins/kotlin/maven/tests/test/org/jetbrains/kotlin/idea/maven/configuration/KotlinSourceRootDirsMavenTest.kt +++ b/plugins/kotlin/maven/tests/test/org/jetbrains/kotlin/idea/maven/configuration/KotlinSourceRootDirsMavenTest.kt @@ -401,6 +401,200 @@ class KotlinSourceRootDirsMavenTest : AbstractKotlinMavenImporterTest() { doTest(project, module, afterFile) } + @Test + fun `test root module with plugin management and processor submodule`() = runBlocking { + val mainPom = createProjectPom( + """ + org.example + project + 1.0-SNAPSHOT + pom + + sub-mod + + + + 17 + 17 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.jetbrains.kotlin + kotlin-maven-plugin + $KOTLIN_VERSION + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + + + """.trimIndent() + ) + + val subModulePom = createModulePom( + "sub-mod", + """ + + org.example + project + 1.0-SNAPSHOT + + + sub-mod + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + 1.6.3 + + + + + + + """.trimIndent() + ) + + importProjectsAsync(mainPom, subModulePom) + assertModules("project", "sub-mod") + + val module = project.modules.first { it.name == "project" } + + val afterFile = """ + + 4.0.0 + org.example + project + 1.0-SNAPSHOT + pom + + sub-mod + + + + 17 + 17 + 2.2.20 + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${'$'}{kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${'$'}{kotlin.version} + test + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${'$'}{kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + ${'$'}{maven.compiler.target} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + + +""" + + val childTextBefore = runReadAction { subModulePom.toPsiFile(project)!!.text } + + doTest(project, module, afterFile) + + val childTextAfter = runReadAction { subModulePom.toPsiFile(project)!!.text } + assertEquals( + "Submodule POM should stay untouched:\n$childTextAfter", + childTextBefore, + childTextAfter + ) + } + private fun doTest(project: Project, module: Module, afterFile: String) { return runInEdtAndGet { val moduleRootManager = ModuleRootManager.getInstance(module)