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:
@@ -32,6 +32,8 @@ import org.jetbrains.idea.maven.dom.MavenDomElement
|
|||||||
import org.jetbrains.idea.maven.dom.MavenDomUtil
|
import org.jetbrains.idea.maven.dom.MavenDomUtil
|
||||||
import org.jetbrains.idea.maven.dom.model.*
|
import org.jetbrains.idea.maven.dom.model.*
|
||||||
import org.jetbrains.idea.maven.model.MavenId
|
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.idea.maven.utils.MavenArtifactScope
|
||||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||||
import org.jetbrains.kotlin.idea.configuration.RepositoryDescription
|
import org.jetbrains.kotlin.idea.configuration.RepositoryDescription
|
||||||
@@ -40,7 +42,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PomFile(val xmlFile: XmlFile) {
|
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 nodesByName = HashMap<String, XmlTag>()
|
||||||
private val projectElement: XmlTag
|
private val projectElement: XmlTag
|
||||||
|
|
||||||
@@ -132,6 +134,34 @@ class PomFile(val xmlFile: XmlFile) {
|
|||||||
return plugin
|
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 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(vararg goals: String) = findKotlinExecutions().filter { it.goals.goals.any { it.rawText in goals } }
|
||||||
fun findKotlinExecutions() = findKotlinPlugins().flatMap { it.executions.executions }
|
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 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 {
|
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(executionId.isNotEmpty()) { "executionId shouldn't be empty" }
|
||||||
require(phase.isNotEmpty()) { "phase 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()
|
val execution = plugin.executions.executions.firstOrNull { it.id.stringValue == executionId } ?: plugin.executions.addExecution()
|
||||||
execution.id.stringValue = executionId
|
execution.id.stringValue = executionId
|
||||||
execution.phase.stringValue = phase
|
execution.phase.stringValue = phase
|
||||||
execution.goals.ensureTagExists()
|
|
||||||
|
|
||||||
val existingGoals = execution.goals.goals.mapNotNull { it.rawText }
|
val existingGoals = execution.goals.goals.mapNotNull { it.rawText }
|
||||||
for (goal in goals.filter { it !in existingGoals }) {
|
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)
|
execution.goals.xmlTag.add(goalTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +198,54 @@ class PomFile(val xmlFile: XmlFile) {
|
|||||||
executionSourceDirs(execution, sourceDirs)
|
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) {
|
fun executionSourceDirs(execution: MavenDomPluginExecution, sourceDirs: List<String>, forceSingleSource: Boolean = false) {
|
||||||
ensureBuild()
|
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 defaultDir = if (isTest) "test" else "main"
|
||||||
val singleDirectoryElement = if (isTest) {
|
val singleDirectoryElement = if (isTest) {
|
||||||
domModel.build.testSourceDirectory
|
domModel.build.testSourceDirectory
|
||||||
@@ -202,10 +274,10 @@ class PomFile(val xmlFile: XmlFile) {
|
|||||||
|
|
||||||
fun executionSourceDirs(execution: MavenDomPluginExecution): List<String> {
|
fun executionSourceDirs(execution: MavenDomPluginExecution): List<String> {
|
||||||
return execution.configuration.xmlTag
|
return execution.configuration.xmlTag
|
||||||
.getChildrenOfType<XmlTag>().firstOrNull { it.localName == "sourceDirs" }
|
.getChildrenOfType<XmlTag>().firstOrNull { it.localName == "sourceDirs" }
|
||||||
?.getChildrenOfType<XmlTag>()
|
?.getChildrenOfType<XmlTag>()
|
||||||
?.map { it.getChildrenOfType<XmlText>().joinToString("") { it.text } }
|
?.map { it.getChildrenOfType<XmlText>().joinToString("") { it.text } }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executionConfiguration(execution: MavenDomPluginExecution, name: String): XmlTag {
|
fun executionConfiguration(execution: MavenDomPluginExecution, name: String): XmlTag {
|
||||||
@@ -370,6 +442,7 @@ class PomFile(val xmlFile: XmlFile) {
|
|||||||
|
|
||||||
@Suppress("Unused")
|
@Suppress("Unused")
|
||||||
object DefaultPhases {
|
object DefaultPhases {
|
||||||
|
val None = "none"
|
||||||
val Validate = "validate"
|
val Validate = "validate"
|
||||||
val Initialize = "initialize"
|
val Initialize = "initialize"
|
||||||
val GenerateSources = "generate-sources"
|
val GenerateSources = "generate-sources"
|
||||||
@@ -403,6 +476,7 @@ class PomFile(val xmlFile: XmlFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@Deprecated("We shouldn't use phase but additional compiler configuration in most cases")
|
||||||
fun getPhase(hasJavaFiles: Boolean, isTest: Boolean) = when {
|
fun getPhase(hasJavaFiles: Boolean, isTest: Boolean) = when {
|
||||||
hasJavaFiles -> when {
|
hasJavaFiles -> when {
|
||||||
isTest -> DefaultPhases.ProcessTestSources
|
isTest -> DefaultPhases.ProcessTestSources
|
||||||
|
|||||||
+6
-1
@@ -150,7 +150,12 @@ abstract class KotlinMavenConfigurator protected constructor(private val stdlibA
|
|||||||
goalName: String,
|
goalName: String,
|
||||||
module: Module,
|
module: Module,
|
||||||
isTest: Boolean) {
|
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 {
|
companion object {
|
||||||
|
|||||||
+31
-6
@@ -21,6 +21,7 @@ import com.intellij.codeInspection.ProblemDescriptor
|
|||||||
import com.intellij.ide.highlighter.JavaFileType
|
import com.intellij.ide.highlighter.JavaFileType
|
||||||
import com.intellij.lang.annotation.HighlightSeverity
|
import com.intellij.lang.annotation.HighlightSeverity
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.module.ModuleManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.search.FileTypeIndex
|
import com.intellij.psi.search.FileTypeIndex
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
@@ -85,10 +86,25 @@ class KotlinMavenPluginPhaseInspection : DomElementsInspection<MavenDomProjectMo
|
|||||||
else {
|
else {
|
||||||
if (hasJavaFiles) {
|
if (hasJavaFiles) {
|
||||||
pom.findExecutions(kotlinPlugin, PomFile.KotlinGoals.Compile).notAtPhase(PomFile.DefaultPhases.ProcessSources).forEach { badExecution ->
|
pom.findExecutions(kotlinPlugin, PomFile.KotlinGoals.Compile).notAtPhase(PomFile.DefaultPhases.ProcessSources).forEach { badExecution ->
|
||||||
holder.createProblem(badExecution.phase.createStableCopy(),
|
val javacPlugin = mavenProject.findPlugin("org.apache.maven.plugins", "maven-compiler-plugin")
|
||||||
HighlightSeverity.WARNING,
|
val existingJavac = pom.domModel.build.plugins.plugins.firstOrNull {
|
||||||
"Kotlin plugin should run before javac so kotlin classes could be visible from Java",
|
it.groupId.stringValue == "org.apache.maven.plugins" &&
|
||||||
FixExecutionPhaseLocalFix(badExecution, PomFile.DefaultPhases.ProcessSources))
|
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 ->
|
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 stdlibDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavaMavenConfigurator.STD_LIB_ID)
|
||||||
val jsDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavascriptMavenConfigurator.STD_LIB_ID)
|
val jsDependencies = mavenProject.findDependencies(KotlinMavenConfigurator.GROUP_ID, KotlinJavascriptMavenConfigurator.STD_LIB_ID)
|
||||||
|
|
||||||
if (hasJvmExecution && stdlibDependencies.isEmpty()) {
|
if (hasJvmExecution && stdlibDependencies.isEmpty()) {
|
||||||
holder.createProblem(kotlinPlugin.artifactId.createStableCopy(),
|
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 {
|
private class FixAddStdlibLocalFix(val pomFile: XmlFile, val id: String, val version: String?) : LocalQuickFix {
|
||||||
override fun getName() = "Add $id dependency"
|
override fun getName() = "Add $id dependency"
|
||||||
override fun getFamilyName() = "Add 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 -->
|
||||||
Reference in New Issue
Block a user