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.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
|
||||
|
||||
+6
-1
@@ -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 {
|
||||
|
||||
+31
-6
@@ -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 -->
|
||||
Reference in New Issue
Block a user