KT-13638 Update Maven configuration actions in IDE according to changes in https://github.com/JetBrains/kotlin-web-site/pull/458

This commit is contained in:
Sergey Mashkov
2016-08-31 19:55:31 +03:00
parent dafe7e3539
commit 297248dc4f
4 changed files with 191 additions and 16 deletions
@@ -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<String, XmlTag>()
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<String>): 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<String>, 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<String> {
return execution.configuration.xmlTag
.getChildrenOfType<XmlTag>().firstOrNull { it.localName == "sourceDirs" }
?.getChildrenOfType<XmlTag>()
?.map { it.getChildrenOfType<XmlText>().joinToString("") { it.text } }
?: emptyList()
.getChildrenOfType<XmlTag>().firstOrNull { it.localName == "sourceDirs" }
?.getChildrenOfType<XmlTag>()
?.map { it.getChildrenOfType<XmlText>().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
@@ -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 {
@@ -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<MavenDomProjectMo
else {
if (hasJavaFiles) {
pom.findExecutions(kotlinPlugin, PomFile.KotlinGoals.Compile).notAtPhase(PomFile.DefaultPhases.ProcessSources).forEach { badExecution ->
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<MavenDomProjectMo
}
}
val stdlibDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavaMavenConfigurator.STD_LIB_ID)
val jsDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavascriptMavenConfigurator.STD_LIB_ID)
val stdlibDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavaMavenConfigurator.STD_LIB_ID)
val jsDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavascriptMavenConfigurator.STD_LIB_ID)
if (hasJvmExecution && stdlibDependencies.isEmpty()) {
holder.createProblem(kotlinPlugin.artifactId.createStableCopy(),
@@ -168,6 +184,15 @@ class KotlinMavenPluginPhaseInspection : DomElementsInspection<MavenDomProjectMo
}
}
private class AddJavaExecutionsLocalFix(val module: Module, val file: XmlFile, val kotlinPlugin: MavenDomPlugin) : LocalQuickFix {
override fun getName() = "Configure maven-compiler-plugin executions in the right order"
override fun getFamilyName() = getName()
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
PomFile(file).addJavacExecutions(module, kotlinPlugin)
}
}
private class FixAddStdlibLocalFix(val pomFile: XmlFile, val id: String, val version: String?) : LocalQuickFix {
override fun getName() = "Add $id dependency"
override fun getFamilyName() = "Add dependency"
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jetbrains.kotlin.test</groupId>
<artifactId>configure-maven-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.version>1.0.1</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<!-- mkjava -->
<!-- problem: on compile, title Kotlin plugin should run before javac so kotlin classes could be visible from Java -->