From 297248dc4fd8169b624f0fb5b261161e2d399bca Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Wed, 31 Aug 2016 19:55:31 +0300 Subject: [PATCH] KT-13638 Update Maven configuration actions in IDE according to changes in https://github.com/JetBrains/kotlin-web-site/pull/458 --- .../jetbrains/kotlin/idea/maven/PomFile.kt | 92 +++++++++++++++++-- .../configuration/KotlinMavenConfigurator.kt | 7 +- .../KotlinMavenPluginPhaseInspection.kt | 37 ++++++-- .../wrongPhaseExecution.fixed.2.xml | 71 ++++++++++++++ 4 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 idea/idea-maven/testData/maven-inspections/wrongPhaseExecution.fixed.2.xml diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt index 58be94958fa..265661e9b2d 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/PomFile.kt @@ -32,6 +32,8 @@ import org.jetbrains.idea.maven.dom.MavenDomElement import org.jetbrains.idea.maven.dom.MavenDomUtil import org.jetbrains.idea.maven.dom.model.* import org.jetbrains.idea.maven.model.MavenId +import org.jetbrains.idea.maven.model.MavenPlugin +import org.jetbrains.idea.maven.project.MavenProjectsManager import org.jetbrains.idea.maven.utils.MavenArtifactScope import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.kotlin.idea.configuration.RepositoryDescription @@ -40,7 +42,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import java.util.* class PomFile(val xmlFile: XmlFile) { - private val domModel = MavenDomUtil.getMavenDomProjectModel(xmlFile.project, xmlFile.virtualFile) ?: throw IllegalStateException("No DOM model found for pom ${xmlFile.name}") + val domModel = MavenDomUtil.getMavenDomProjectModel(xmlFile.project, xmlFile.virtualFile) ?: throw IllegalStateException("No DOM model found for pom ${xmlFile.name}") private val nodesByName = HashMap() private val projectElement: XmlTag @@ -132,6 +134,34 @@ class PomFile(val xmlFile: XmlFile) { return plugin } + 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!! + + while (e !== referenceElement) { + val prev = e.prevSibling ?: return false + e = prev + } + + return true + } + + fun ensurePluginAfter(plugin: MavenDomPlugin, referencePlugin: MavenDomPlugin): MavenDomPlugin { + 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 plugin + } + fun findKotlinPlugins() = domModel.build.plugins.plugins.filter { it.isKotlinMavenPlugin() } fun findKotlinExecutions(vararg goals: String) = findKotlinExecutions().filter { it.goals.goals.any { it.rawText in goals } } fun findKotlinExecutions() = findKotlinPlugins().flatMap { it.executions.executions } @@ -140,18 +170,16 @@ class PomFile(val xmlFile: XmlFile) { fun findExecutions(plugin: MavenDomPlugin, vararg goals: String) = findExecutions(plugin).filter { it.goals.goals.any { it.rawText in goals } } fun addExecution(plugin: MavenDomPlugin, executionId: String, phase: String, goals: List): MavenDomPluginExecution { - require(goals.isNotEmpty()) { "Execution $executionId requires at least one goal but empty list has been provided" } require(executionId.isNotEmpty()) { "executionId shouldn't be empty" } require(phase.isNotEmpty()) { "phase shouldn't be empty" } val execution = plugin.executions.executions.firstOrNull { it.id.stringValue == executionId } ?: plugin.executions.addExecution() execution.id.stringValue = executionId execution.phase.stringValue = phase - execution.goals.ensureTagExists() val existingGoals = execution.goals.goals.mapNotNull { it.rawText } for (goal in goals.filter { it !in existingGoals }) { - val goalTag = execution.goals.xmlTag.createChildTag("goal", goal) + val goalTag = execution.goals.ensureTagExists().createChildTag("goal", goal) execution.goals.xmlTag.add(goalTag) } @@ -170,10 +198,54 @@ class PomFile(val xmlFile: XmlFile) { executionSourceDirs(execution, sourceDirs) } + fun isPluginExecutionMissing(plugin: MavenPlugin?, excludedExecutionId: String, goal: String) = plugin == null || plugin.executions.none { it.executionId != excludedExecutionId && goal in it.goals } + + fun addJavacExecutions(module: Module, kotlinPlugin: MavenDomPlugin) { + val javacPlugin = ensurePluginAfter(addPlugin(MavenId("org.apache.maven.plugins", "maven-compiler-plugin", null)), kotlinPlugin) + + val project = MavenProjectsManager.getInstance(module.project).findProject(module)!! + val plugin = project.findPlugin("org.apache.maven.plugins", "maven-compiler-plugin") + + if (isExecutionEnabled(plugin, "default-compile")) { + addExecution(javacPlugin, "default-compile", "none", emptyList()) + } + + if (isExecutionEnabled(plugin, "default-testCompile")) { + addExecution(javacPlugin, "default-testCompile", "none", emptyList()) + } + + if (isPluginExecutionMissing(plugin, "default-compile", "compile")) { + addExecution(javacPlugin, "compile", PomFile.DefaultPhases.Compile, listOf("compile")) + } + + if (isPluginExecutionMissing(plugin, "default-testCompile", "testCompile")) { + addExecution(javacPlugin, "testCompile", PomFile.DefaultPhases.TestCompile, listOf("testCompile")) + } + } + + fun isExecutionEnabled(plugin: MavenPlugin?, executionId: String): Boolean { + if (plugin == null) { + return true + } + + if (domModel.build.plugins.plugins.any { + it.groupId.stringValue == "org.apache.maven.plugins" + && it.artifactId.stringValue == "maven-compiler-plugin" + && it.executions.executions.any { it.id.stringValue == executionId && it.phase.stringValue == DefaultPhases.None } + }) { + return false + } + + // TODO: getPhase has been added as per https://youtrack.jetbrains.com/issue/IDEA-153582 and available only in latest IDEAs + return plugin.executions.filter { it.executionId == executionId }.all { execution -> + execution.javaClass.methods.filter { it.name == "getPhase" && it.parameterTypes.isEmpty() }.all { it.invoke(execution) == DefaultPhases.None } + } + } + fun executionSourceDirs(execution: MavenDomPluginExecution, sourceDirs: List, forceSingleSource: Boolean = false) { ensureBuild() - val isTest = execution.goals.goals.any { it.stringValue == KotlinGoals.TestCompile || it.stringValue == KotlinGoals.TestJs} + val isTest = execution.goals.goals.any { it.stringValue == KotlinGoals.TestCompile || it.stringValue == KotlinGoals.TestJs } val defaultDir = if (isTest) "test" else "main" val singleDirectoryElement = if (isTest) { domModel.build.testSourceDirectory @@ -202,10 +274,10 @@ class PomFile(val xmlFile: XmlFile) { fun executionSourceDirs(execution: MavenDomPluginExecution): List { return execution.configuration.xmlTag - .getChildrenOfType().firstOrNull { it.localName == "sourceDirs" } - ?.getChildrenOfType() - ?.map { it.getChildrenOfType().joinToString("") { it.text } } - ?: emptyList() + .getChildrenOfType().firstOrNull { it.localName == "sourceDirs" } + ?.getChildrenOfType() + ?.map { it.getChildrenOfType().joinToString("") { it.text } } + ?: emptyList() } fun executionConfiguration(execution: MavenDomPluginExecution, name: String): XmlTag { @@ -370,6 +442,7 @@ class PomFile(val xmlFile: XmlFile) { @Suppress("Unused") object DefaultPhases { + val None = "none" val Validate = "validate" val Initialize = "initialize" val GenerateSources = "generate-sources" @@ -403,6 +476,7 @@ class PomFile(val xmlFile: XmlFile) { } companion object { + @Deprecated("We shouldn't use phase but additional compiler configuration in most cases") fun getPhase(hasJavaFiles: Boolean, isTest: Boolean) = when { hasJavaFiles -> when { isTest -> DefaultPhases.ProcessTestSources diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt index 5c38bf5f26f..43ea57f9461 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/configuration/KotlinMavenConfigurator.kt @@ -150,7 +150,12 @@ abstract class KotlinMavenConfigurator protected constructor(private val stdlibA goalName: String, module: Module, isTest: Boolean) { - pomFile.addKotlinExecution(module, kotlinPlugin, executionId, PomFile.getPhase(hasJavaFiles(module), isTest), isTest, listOf(goalName)) + + pomFile.addKotlinExecution(module, kotlinPlugin, executionId, PomFile.getPhase(false, isTest), isTest, listOf(goalName)) + + if (hasJavaFiles(module)) { + pomFile.addJavacExecutions(module, kotlinPlugin) + } } companion object { diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/inspections/KotlinMavenPluginPhaseInspection.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/inspections/KotlinMavenPluginPhaseInspection.kt index ecf2ce70c2e..6c1c884a500 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/inspections/KotlinMavenPluginPhaseInspection.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/inspections/KotlinMavenPluginPhaseInspection.kt @@ -21,6 +21,7 @@ import com.intellij.codeInspection.ProblemDescriptor import com.intellij.ide.highlighter.JavaFileType import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope @@ -85,10 +86,25 @@ class KotlinMavenPluginPhaseInspection : DomElementsInspection - holder.createProblem(badExecution.phase.createStableCopy(), - HighlightSeverity.WARNING, - "Kotlin plugin should run before javac so kotlin classes could be visible from Java", - FixExecutionPhaseLocalFix(badExecution, PomFile.DefaultPhases.ProcessSources)) + val javacPlugin = mavenProject.findPlugin("org.apache.maven.plugins", "maven-compiler-plugin") + val existingJavac = pom.domModel.build.plugins.plugins.firstOrNull { + it.groupId.stringValue == "org.apache.maven.plugins" && + it.artifactId.stringValue == "maven-compiler-plugin" + } + + if (existingJavac == null + || !pom.isPluginAfter(existingJavac, kotlinPlugin) + || pom.isExecutionEnabled(javacPlugin, "default-compile") + || pom.isExecutionEnabled(javacPlugin, "default-testCompile") + || pom.isPluginExecutionMissing(javacPlugin, "default-compile", "compile") + || pom.isPluginExecutionMissing(javacPlugin, "default-testCompile", "testCompile")) { + + holder.createProblem(badExecution.phase.createStableCopy(), + HighlightSeverity.WARNING, + "Kotlin plugin should run before javac so kotlin classes could be visible from Java", + FixExecutionPhaseLocalFix(badExecution, PomFile.DefaultPhases.ProcessSources), + AddJavaExecutionsLocalFix(module, domFileElement.file, kotlinPlugin)) + } } pom.findExecutions(kotlinPlugin, PomFile.KotlinGoals.Js, PomFile.KotlinGoals.TestJs).forEach { badExecution -> @@ -98,8 +114,8 @@ class KotlinMavenPluginPhaseInspection : DomElementsInspection + + 4.0.0 + + org.jetbrains.kotlin.test + configure-maven-test + 1.0-SNAPSHOT + + + 1.0.1 + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + + + + + \ No newline at end of file