Introduce importing checkers with implementation for orphan source sets

* Found diagnostics are attaching to the model
* Diagnostics are collecting into Module's DataNode on IDE side
* Some tests for diagnostics are added
This commit is contained in:
yaroslav.chernyshev
2021-01-27 16:50:15 +03:00
committed by Yaroslav.Chernyshev
parent 87e130e77a
commit b2017a9c9c
10 changed files with 107 additions and 15 deletions
@@ -27,8 +27,11 @@ import com.intellij.openapi.externalSystem.model.Key as ExternalKey
var DataNode<out ModuleData>.kotlinSourceSet: KotlinSourceSetInfo?
by CopyableDataNodeUserDataProperty(Key.create("KOTLIN_SOURCE_SET"))
var DataNode<out ModuleData>.kotlinImportingDiagnosticsContainer: KotlinImportingDiagnosticsContainer?
by CopyableDataNodeUserDataProperty(Key.create("KOTLIN_IMPORTING_DIAGNOSTICS_CONTAINER"))
val DataNode<ModuleData>.kotlinAndroidSourceSets: List<KotlinSourceSetInfo>?
get() = ExternalSystemApiUtil.getChildren(this, KotlinAndroidSourceSetData.KEY).firstOrNull()?.data?.sourceSetInfos
get() = ExternalSystemApiUtil.getChildren(this, KotlinAndroidSourceSetData.KEY).firstOrNull()?.data?.sourceSetInfos
class KotlinSourceSetInfo @PropertyMapping("kotlinModule") constructor(val kotlinModule: KotlinModule) : Serializable {
var moduleId: String? = null
@@ -61,7 +61,6 @@ import java.io.File
import java.lang.reflect.Proxy
import java.util.*
import java.util.stream.Collectors
import kotlin.collections.HashMap
@Order(ExternalSystemConstants.UNORDERED + 1)
open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionCompat() {
@@ -458,9 +457,12 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
}
}
mainModuleNode.kotlinNativeHome = mppModel.kotlinNativeHome
mainModuleNode.coroutines = mppModel.extraFeatures.coroutinesState
mainModuleNode.isHmpp = mppModel.extraFeatures.isHMPPEnabled
with(mainModuleNode) {
kotlinNativeHome = mppModel.kotlinNativeHome
coroutines = mppModel.extraFeatures.coroutinesState
isHmpp = mppModel.extraFeatures.isHMPPEnabled
kotlinImportingDiagnosticsContainer = mppModel.kotlinImportingDiagnostics
}
//TODO improve passing version of used multiplatform
}
@@ -782,7 +784,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
if (fromModule.data == toModule.data) return
val fromData = fromModule.data as? ModuleData ?: return
val toData = toModule.data as? ModuleData ?: return
val existing = fromModule.children.mapNotNull { it.data as? ModuleDependencyData }.filter { it.target.id == (toModule.data as? ModuleData)?.id }
val existing = fromModule.children.mapNotNull { it.data as? ModuleDependencyData }
.filter { it.target.id == (toModule.data as? ModuleData)?.id }
val nodeToModify =
existing.singleOrNull() ?: existing.firstOrNull { it.scope == DependencyScope.COMPILE } ?: existing.firstOrNull()
if (nodeToModify != null) {
@@ -8,7 +8,10 @@ package org.jetbrains.kotlin.gradle
import com.intellij.openapi.roots.DependencyScope
import org.jetbrains.jps.model.java.JavaResourceRootType
import org.jetbrains.jps.model.java.JavaSourceRootType
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.ResourceKotlinRootType
import org.jetbrains.kotlin.config.SourceKotlinRootType
import org.jetbrains.kotlin.config.TestResourceKotlinRootType
import org.jetbrains.kotlin.config.TestSourceKotlinRootType
import org.jetbrains.kotlin.idea.codeInsight.gradle.MultiplePluginVersionGradleImportingTestCase
import org.jetbrains.kotlin.idea.codeInsight.gradle.mppImportTestMinVersionForMaster
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -440,6 +443,9 @@ class HierarchicalMultiplatformProjectImportingTest : MultiplePluginVersionGradl
module("my-app.orphan") {
targetPlatform(jvm, js)
}
module("my-app") {
diagnostics(OrphanSourceSetsImportingDiagnostic::class.java to 1)
}
}
}
@@ -529,6 +535,10 @@ class HierarchicalMultiplatformProjectImportingTest : MultiplePluginVersionGradl
checkProjectStructure(exhaustiveModuleList = false, exhaustiveSourceSourceRootList = false, exhaustiveDependencyList = false) {
module("my-app") {
diagnostics(OrphanSourceSetsImportingDiagnostic::class.java to 3)
}
// (jvm, js, native) is highly undesirable
module("my-app.danglingOnJvm") {
targetPlatform(jvm, js)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
@@ -14,13 +15,16 @@ import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
import org.jetbrains.jps.util.JpsPathUtil
import org.jetbrains.kotlin.config.ExternalSystemTestRunTask
import org.jetbrains.kotlin.idea.configuration.kotlinImportingDiagnosticsContainer
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.facet.externalSystemTestRunTasks
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.project.isHMPPEnabled
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.presentableDescription
import org.jetbrains.plugins.gradle.util.GradleUtil
class MessageCollector {
private val builder = StringBuilder()
@@ -37,6 +41,7 @@ class MessageCollector {
}
}
@Suppress("UnstableApiUsage")
class ProjectInfo(
project: Project,
internal val projectPath: String,
@@ -47,6 +52,7 @@ class ProjectInfo(
) {
internal val messageCollector = MessageCollector()
private val moduleManager = ModuleManager.getInstance(project)
private val projectDataNode = ExternalSystemApiUtil.findProjectData(project, GRADLE_SYSTEM_ID, projectPath)
private val expectedModuleNames = HashSet<String>()
private var allModulesAsserter: (ModuleInfo.() -> Unit)? = null
@@ -201,7 +207,7 @@ class ModuleInfo(
fun moduleDependency(moduleName: String, scope: DependencyScope, productionOnTest: Boolean? = null) {
val moduleEntries =
rootModel.orderEntries.filterIsInstance<ModuleOrderEntry>().filter { it.moduleName == moduleName && it.scope == scope}
rootModel.orderEntries.filterIsInstance<ModuleOrderEntry>().filter { it.moduleName == moduleName && it.scope == scope }
if (moduleEntries.size > 1) {
projectInfo.messageCollector.report(
"Found multiple order entries for module $moduleName: ${rootModel.orderEntries.filterIsInstance<ModuleOrderEntry>()}"
@@ -270,6 +276,22 @@ class ModuleInfo(
}
}
@Suppress("UnstableApiUsage")
fun diagnostics(vararg expectedByType: Pair<Class<out KotlinImportingDiagnostic>, Int>) {
val moduleNode = GradleUtil.findGradleModuleData(module)
val diagnostics = moduleNode!!.kotlinImportingDiagnosticsContainer!!
expectedByType.forEach { (expectedClazz, expectedCount) ->
val typedDiagnostics = diagnostics.filterIsInstance(expectedClazz)
if (typedDiagnostics.size != expectedCount) {
val actualCount = typedDiagnostics.size
projectInfo.messageCollector.report(
"Expected number of ${expectedClazz.simpleName} diagnostics $expectedCount doesn't match the actual one: $actualCount"
)
}
}
}
fun run(body: ModuleInfo.() -> Unit = {}) {
body()
@@ -287,7 +309,9 @@ class ModuleInfo(
}
}
if ((!module.externalSystemTestRunTasks().containsAll(expectedExternalSystemTestTasks)) || (projectInfo.exhaustiveTestsList && (module.externalSystemTestRunTasks() != expectedExternalSystemTestTasks))) {
if ((!module.externalSystemTestRunTasks()
.containsAll(expectedExternalSystemTestTasks)) || (projectInfo.exhaustiveTestsList && (module.externalSystemTestRunTasks() != expectedExternalSystemTestTasks))
) {
projectInfo.messageCollector.report(
"Module '${module.name}': Expected tests list $expectedExternalSystemTestTasks doesn't match the actual one: ${module.externalSystemTestRunTasks()}"
)
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle
import java.io.Serializable
interface KotlinImportingDiagnostic : Serializable
interface KotlinSourceSetImportingDiagnostic : KotlinImportingDiagnostic {
val kotlinSourceSet: KotlinSourceSet
}
typealias KotlinImportingDiagnosticsContainer = MutableSet<KotlinImportingDiagnostic>
data class OrphanSourceSetsImportingDiagnostic(override val kotlinSourceSet: KotlinSourceSet) : KotlinSourceSetImportingDiagnostic
@@ -180,6 +180,7 @@ interface KotlinMPPGradleModel : Serializable {
val targets: Collection<KotlinTarget>
val extraFeatures: ExtraFeatures
val kotlinNativeHome: String
val kotlinImportingDiagnostics: KotlinImportingDiagnosticsContainer
companion object {
const val NO_KOTLIN_NATIVE_HOME = ""
@@ -76,7 +76,9 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
),
kotlinNativeHome,
dependencyMapper.toDependencyMap()
)
).apply {
kotlinImportingDiagnostics += collectDiagnostics(importingContext)
}
}
private fun filterOrphanSourceSets(
@@ -906,6 +908,17 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
companion object {
private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java)
private val DEFAULT_IMPORTING_CHECKERS = listOf(
OrphanSourceSetImportingChecker
)
private fun KotlinMPPGradleModel.collectDiagnostics(importingContext: MultiplatformModelImportingContext): KotlinImportingDiagnosticsContainer =
mutableSetOf<KotlinImportingDiagnostic>().apply {
DEFAULT_IMPORTING_CHECKERS.forEach {
it.check(this@collectDiagnostics, this, importingContext)
}
}
fun Project.getTargets(): Collection<Named>? {
val kotlinExt = project.extensions.findByName("kotlin") ?: return null
val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null
@@ -243,7 +243,8 @@ data class KotlinMPPGradleModelImpl(
override val targets: Collection<KotlinTarget>,
override val extraFeatures: ExtraFeatures,
override val kotlinNativeHome: String,
override val dependencyMap: Map<KotlinDependencyId, KotlinDependency>
override val dependencyMap: Map<KotlinDependencyId, KotlinDependency>,
override val kotlinImportingDiagnostics: KotlinImportingDiagnosticsContainer = mutableSetOf()
) : KotlinMPPGradleModel {
constructor(mppModel: KotlinMPPGradleModel, cloningCache: MutableMap<Any, Any>) : this(
@@ -264,7 +265,8 @@ data class KotlinMPPGradleModelImpl(
mppModel.extraFeatures.isNativeDependencyPropagationEnabled
),
mppModel.kotlinNativeHome,
mppModel.dependencyMap.map { it.key to it.value.deepCopy(cloningCache) }.toMap()
mppModel.dependencyMap.map { it.key to it.value.deepCopy(cloningCache) }.toMap(),
mppModel.kotlinImportingDiagnostics.toMutableSet()
)
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle
internal interface MultiplatformModelImportingChecker {
fun check(model: KotlinMPPGradleModel, reportTo: KotlinImportingDiagnosticsContainer, context: MultiplatformModelImportingContext)
}
internal object OrphanSourceSetImportingChecker : MultiplatformModelImportingChecker {
override fun check(
model: KotlinMPPGradleModel,
reportTo: KotlinImportingDiagnosticsContainer,
context: MultiplatformModelImportingContext
) = model.sourceSets.values.filter { context.isOrphanSourceSet(it) }
.map { OrphanSourceSetsImportingDiagnostic(it) }
.forEach { reportTo += it }
}
@@ -7,8 +7,6 @@ package org.jetbrains.kotlin.gradle
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.GradleImportProperties.*
import java.lang.Exception
private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java)
@@ -69,7 +67,6 @@ internal class MultiplatformModelImportingContextImpl(override val project: Proj
override val sourceSets: Collection<KotlinSourceSetImpl>
get() = sourceSetsByNames.values
/** see [initializeCompilations] */
override lateinit var compilations: Collection<KotlinCompilation>
private set