diff --git a/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/kpm/dependency.kt b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/kpm/dependency.kt index dea4ff1564f..09d6fdab60c 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/kpm/dependency.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/proto/kpm/dependency.kt @@ -15,7 +15,6 @@ import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.IdeaKpmDependencyPro import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.IdeaKpmDependencyProto.DependencyCase import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.ideaKpmDependencyProto import org.jetbrains.kotlin.gradle.idea.serialize.IdeaSerializationContext -import org.jetbrains.kotlin.gradle.kpm.idea.* internal fun IdeaSerializationContext.IdeaKpmDependencyProto(dependency: IdeaKpmDependency): IdeaKpmDependencyProto { return ideaKpmDependencyProto { diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryDependency.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryDependency.kt index bc3145630cc..825fc17c586 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryDependency.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryDependency.kt @@ -17,10 +17,18 @@ data class IdeaKotlinResolvedBinaryDependency( val binaryFile: File, override val extras: MutableExtras, override val coordinates: IdeaKotlinBinaryCoordinates? -) : IdeaKotlinBinaryDependency() +) : IdeaKotlinBinaryDependency() { + internal companion object { + const val serialVersionUID = 0L + } +} data class IdeaKotlinUnresolvedBinaryDependency( val cause: String?, override val coordinates: IdeaKotlinBinaryCoordinates?, override val extras: MutableExtras -) : IdeaKotlinBinaryDependency() \ No newline at end of file +) : IdeaKotlinBinaryDependency() { + internal companion object { + const val serialVersionUID = 0L + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt index b9c8a0828e1..99b23c587a9 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt @@ -5,7 +5,9 @@ package org.jetbrains.kotlin.gradle.idea.tcs -sealed interface IdeaKotlinDependencyCoordinates +import java.io.Serializable + +sealed interface IdeaKotlinDependencyCoordinates : Serializable data class IdeaKotlinBinaryCoordinates( val group: String, @@ -16,6 +18,11 @@ data class IdeaKotlinBinaryCoordinates( override fun toString(): String { return "$group:$module:$version${sourceSetName?.let { ":$it" }.orEmpty()}" } + + internal companion object { + const val serialVersionUID = 0L + } + } data class IdeaKotlinSourceCoordinates( @@ -27,4 +34,8 @@ data class IdeaKotlinSourceCoordinates( override fun toString(): String { return "${buildId.takeIf { it != ":" }.orEmpty()}$projectPath/$sourceSetName" } + + internal companion object { + const val serialVersionUID = 0L + } } diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinSourceDependency.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinSourceDependency.kt index e48659e63c2..3107df3da26 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinSourceDependency.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinSourceDependency.kt @@ -14,6 +14,14 @@ data class IdeaKotlinSourceDependency( override val extras: MutableExtras ) : IdeaKotlinDependency { enum class Type : Serializable { - Regular, Friend, DependsOn + Regular, Friend, DependsOn; + + internal companion object { + const val serialVersionUID = 0L + } + } + + internal companion object { + const val serialVersionUID = 0L } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinObjectGraphTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinObjectGraphTest.kt new file mode 100644 index 00000000000..88c136983fb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinObjectGraphTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2022 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.idea.tcs + +import org.jetbrains.kotlin.tooling.core.AbstractExtras +import org.jetbrains.kotlin.tooling.core.Extras +import org.jetbrains.kotlin.tooling.core.MutableExtras +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.reflections.Reflections +import org.reflections.scanners.Scanners +import java.io.Serializable +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import kotlin.reflect.KClass +import kotlin.reflect.full.memberProperties +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +@RunWith(Parameterized::class) +class IdeaKotlinObjectGraphTest(private val node: KClass<*>, private val clazzName: String) { + + @Test + fun `test - node implements Serializable`() { + assertTrue( + Serializable::class.java.isAssignableFrom(node.java), + "Expected clazz $clazzName to implement ${Serializable::class.qualifiedName}`" + ) + } + + @Test + fun `test - node is sealed`() { + if (node.java.isInterface || node.isAbstract) { + assertTrue(node.isSealed, "Expected $clazzName to be sealed") + } + } + + @Test + fun `test - node implementations contain serialVersionUID`() { + if (!node.java.isInterface && !Modifier.isAbstract(node.java.modifiers)) { + val serialVersionUID = assertNotNull( + node.java.getDeclaredFieldOrNull("serialVersionUID"), + "Expected $node to declare 'serialVersionUID' field" + ) + + assertTrue( + Modifier.isStatic(serialVersionUID.modifiers), + "Expected $node to declare 'serialVersionUID' statically" + ) + + assertTrue( + serialVersionUID.type.isPrimitive, + "Expected $node to declare primitive 'serialVersionUID'" + ) + + assertEquals( + serialVersionUID.type, Long::class.javaPrimitiveType, + "Expected $node to declare 'serialVersionUID' of type Long" + ) + } + } + + private fun Class<*>.getDeclaredFieldOrNull(name: String): Field? { + return try { + getDeclaredField(name) + } catch (t: NoSuchFieldException) { + return null + } + } + + + companion object { + private val reflections = Reflections("org.jetbrains.kotlin") + private val ideaTcsReflections = Reflections("org.jetbrains.kotlin.gradle.idea.tcs") + private val ignoredNodes = setOf( + /* + Extras interface and AbstractExtras are okay for now: + Let's check known implementations for correctness + */ + Extras::class, MutableExtras::class, AbstractExtras::class + ) + + @JvmStatic + @Parameters(name = "{1}") + fun findClasses(): List> { + val classes = mutableSetOf>() + + val resolveQueue = ArrayDeque>() + + resolveQueue += ideaTcsReflections.getAll(Scanners.SubTypes) + .map { Class.forName(it) } + .filter { it.name.startsWith("org.jetbrains.kotlin.gradle.idea.tcs") } + .map { it.kotlin } + + while (resolveQueue.isNotEmpty()) { + val next = resolveQueue.removeFirst() + if (!classes.add(next)) continue + + next.resolveReachableClasses().forEach { child -> + resolveQueue.add(child) + if (child.java.isInterface || Modifier.isAbstract(child.java.modifiers)) { + val subtypes = reflections.getSubTypesOf(child.java).map { it.kotlin } + assertTrue(subtypes.isNotEmpty(), "Missing implementations for $child") + resolveQueue.addAll(subtypes) + } + } + } + + return classes + .filter { it !in ignoredNodes } + .map { clazz -> arrayOf(clazz, checkNotNull(clazz.displayName())) } + + } + + private fun KClass<*>.displayName() = java.name + .removePrefix("org.jetbrains.kotlin") + .removePrefix(".gradle") + .removePrefix(".idea") + .removePrefix(".tcs") + .removePrefix(".") + + private fun KClass<*>.resolveReachableClasses(): Set> { + return this.memberProperties + .map { member -> member.returnType } + .flatMap { type -> setOf(type) + type.arguments.mapNotNull { it.type } } + .mapNotNull { type -> type.classifier as? KClass<*> } + .filter { clazz -> clazz.java.name.startsWith("org.jetbrains") } + .toSet() + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/kpm/assertions.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/kpm/assertions.kt index b4c723fa4c5..07d60f22fcf 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/kpm/assertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/testFixtures/kotlin/org/jetbrains/kotlin/gradle/idea/testFixtures/kpm/assertions.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.idea.testFixtures.kpm import org.jetbrains.kotlin.gradle.idea.kpm.* -import org.jetbrains.kotlin.gradle.kpm.idea.* import kotlin.test.fail fun IdeaKpmProject.assertIsNotEmpty(): IdeaKpmProject = apply {