[Gradle] Initial port of IdeaKpmProjectModelBuilder into TCS IdeMultiplatformImport

KT-54825
This commit is contained in:
Sebastian Sellmair
2022-11-08 09:56:28 +01:00
committed by Space Team
parent c727dbb8ca
commit 405c94e0bb
18 changed files with 859 additions and 1 deletions
@@ -0,0 +1,55 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.tooling.core.Extras
import java.io.File
import java.io.Serializable
sealed class IdeDependency {
abstract val coordinates: IdeDependencyCoordinates?
abstract val extras: Extras
/* Reflection helper */
val isSourceDependency: Boolean get() = this is IdeSourceDependency
val isBinaryDependency: Boolean get() = this is IdeBinaryDependency
val isResolvedBinaryDependency: Boolean get() = this is IdeResolvedBinaryDependency
val isUnresolvedBinaryDependency: Boolean get() = this is IdeUnresolvedBinaryDependency
companion object {
const val CLASSPATH_BINARY_TYPE = "org.jetbrains.kotlin.gradle.plugin.ide.IdeDependency.classpathBinaryType"
const val SOURCES_BINARY_TYPE = "org.jetbrains.kotlin.gradle.plugin.ide.IdeDependency.sourcesBinaryType"
const val DOCUMENTATION_BINARY_TYPE = "org.jetbrains.kotlin.gradle.plugin.ide.IdeDependency.documentationBinaryType"
}
}
data class IdeSourceDependency(
val type: Type,
override val coordinates: IdeSourceCoordinates,
override val extras: Extras
) : IdeDependency() {
enum class Type : Serializable {
Regular, Friend, DependsOn
}
}
sealed class IdeBinaryDependency : IdeDependency() {
abstract override val coordinates: IdeBinaryCoordinates?
}
data class IdeUnresolvedBinaryDependency(
val cause: String?,
override val coordinates: IdeBinaryCoordinates?,
override val extras: Extras
) : IdeBinaryDependency()
data class IdeResolvedBinaryDependency(
val binaryType: String,
val binaryFile: File,
override val extras: Extras,
override val coordinates: IdeBinaryCoordinates?
) : IdeBinaryDependency()
@@ -0,0 +1,26 @@
/*
* 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.plugin.ide
sealed class IdeDependencyCoordinates {
/* Reflection helpers */
val isBinaryCoordinates get() = this is IdeBinaryCoordinates
val isSourceCoordinates get() = this is IdeSourceCoordinates
}
data class IdeBinaryCoordinates(
val group: String,
val module: String,
val version: String?,
val sourceSetName: String?
) : IdeDependencyCoordinates()
data class IdeSourceCoordinates(
val buildId: String,
val projectPath: String,
val projectName: String,
val sourceSetName: String
) : IdeDependencyCoordinates()
@@ -0,0 +1,21 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
/**
* An [IdeDependencyEffect] is invoked last in the dependency resolution pipeline.
* It is not intended/capable of changing the resolution result, but can trigger side-effects, being sure
* that the dependency resolution result is final.
*/
fun interface IdeDependencyEffect {
operator fun invoke(sourceSet: KotlinSourceSet, dependencies: Set<IdeDependency>)
}
fun IdeDependencyResolver.withEffect(effect: IdeDependencyEffect) = IdeDependencyResolver { sourceSet ->
this@withEffect.resolve(sourceSet).also { dependencies -> effect(sourceSet, dependencies) }
}
@@ -0,0 +1,51 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
fun interface IdeDependencyResolver {
fun resolve(sourceSet: KotlinSourceSet): Set<IdeDependency>
object Empty : IdeDependencyResolver {
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeDependency> = emptySet()
}
}
fun IdeDependencyResolver(
resolvers: Iterable<IdeDependencyResolver?>
): IdeDependencyResolver {
val resolversList = resolvers.filterNotNull()
if (resolversList.isEmpty()) return IdeDependencyResolver.Empty
return IdeCompositeDependencyResolver(resolversList)
}
fun IdeDependencyResolver(
vararg resolvers: IdeDependencyResolver?
): IdeDependencyResolver = IdeDependencyResolver(resolvers.toList())
operator fun IdeDependencyResolver.plus(other: IdeDependencyResolver): IdeDependencyResolver {
if (this is IdeCompositeDependencyResolver && other is IdeCompositeDependencyResolver)
return IdeCompositeDependencyResolver(this.children + other.children)
if (this is IdeCompositeDependencyResolver) {
return IdeCompositeDependencyResolver(this.children + other)
}
if (other is IdeCompositeDependencyResolver) {
return IdeCompositeDependencyResolver(listOf(this) + other.children)
}
return IdeCompositeDependencyResolver(listOf(this, other))
}
private class IdeCompositeDependencyResolver(
val children: List<IdeDependencyResolver>
) : IdeDependencyResolver {
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeDependency> {
return children.flatMap { child -> child.resolve(sourceSet) }.toSet()
}
}
@@ -0,0 +1,59 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
/**
* An [IdeDependencyTransformer] will be invoked after all [IdeDependencyResolver] finished.
* The transformation step is allowed to entirely modify the resolution result. Typical applications would be dependency filtering,
* or grouping. Adding additional dependencies might be harmful, since other transformers might not be able to see this additional
* dependency, running before the addition.
*
* Transformations can be scheduled in different phases see: [IdeMultiplatformImport.DependencyTransformationPhase]
*/
fun interface IdeDependencyTransformer {
fun transform(sourceSet: KotlinSourceSet, dependencies: Set<IdeDependency>): Set<IdeDependency>
}
fun IdeDependencyResolver.withTransformer(transformer: IdeDependencyTransformer) = IdeDependencyResolver { sourceSet ->
transformer.transform(sourceSet, this@withTransformer.resolve(sourceSet))
}
fun IdeDependencyTransformer(
transformers: List<IdeDependencyTransformer?>
): IdeDependencyTransformer = IdeCompositeDependencyTransformer(transformers.filterNotNull())
fun IdeDependencyTransformer(
vararg transformers: IdeDependencyTransformer?
): IdeDependencyTransformer = IdeDependencyTransformer(transformers.toList())
operator fun IdeDependencyTransformer.plus(other: IdeDependencyTransformer):
IdeDependencyTransformer {
if (this is IdeCompositeDependencyTransformer && other is IdeCompositeDependencyTransformer) {
return IdeCompositeDependencyTransformer(this.transformers + other.transformers)
}
if (this is IdeCompositeDependencyTransformer) {
return IdeCompositeDependencyTransformer(this.transformers + other)
}
if (other is IdeCompositeDependencyTransformer) {
return IdeCompositeDependencyTransformer(listOf(this) + other.transformers)
}
return IdeCompositeDependencyTransformer(listOf(this, other))
}
private class IdeCompositeDependencyTransformer(
val transformers: List<IdeDependencyTransformer>
) : IdeDependencyTransformer {
override fun transform(
sourceSet: KotlinSourceSet, dependencies: Set<IdeDependency>
): Set<IdeDependency> {
return transformers.fold(dependencies) { currentDependencies, transformer -> transformer.transform(sourceSet, currentDependencies) }
}
}
@@ -0,0 +1,46 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.tooling.core.Extras
interface IdeExtrasSerializationExtension {
fun <T : Any> serializer(key: Extras.Key<T>): IdeExtrasSerializer<T>?
object Empty : IdeExtrasSerializationExtension {
override fun <T : Any> serializer(key: Extras.Key<T>): IdeExtrasSerializer<T>? = null
}
}
interface IdeExtrasSerializationExtensionBuilder {
fun <T : Any> register(key: Extras.Key<T>, serializer: IdeExtrasSerializer<T>)
}
fun IdeExtrasSerializationExtension(
builder: IdeExtrasSerializationExtensionBuilder.() -> Unit
): IdeExtrasSerializationExtension {
return IdeaKpmExtrasSerializationExtensionImpl(
IdeaExtrasSerializationExtensionBuilderImpl().apply(builder).serializers
)
}
private class IdeaExtrasSerializationExtensionBuilderImpl : IdeExtrasSerializationExtensionBuilder {
val serializers = mutableMapOf<Extras.Key<*>, IdeExtrasSerializer<*>>()
override fun <T : Any> register(key: Extras.Key<T>, serializer: IdeExtrasSerializer<T>) {
serializers[key] = serializer
}
}
private class IdeaKpmExtrasSerializationExtensionImpl(
private val map: Map<Extras.Key<*>, IdeExtrasSerializer<*>>
) : IdeExtrasSerializationExtension {
override fun <T : Any> serializer(key: Extras.Key<T>): IdeExtrasSerializer<T>? {
@Suppress("unchecked_cast")
return map[key] as? IdeExtrasSerializer<T>
}
}
@@ -0,0 +1,27 @@
/*
* 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.plugin.ide
import kotlin.reflect.KClass
interface IdeExtrasSerializer<T : Any> {
fun serialize(context: IdeSerializationContext, value: T): ByteArray?
fun deserialize(context: IdeSerializationContext, data: ByteArray): T?
companion object {
/**
* Returns a [IdeExtrasSerializer] based upon [java.io.Serializable]
*/
fun <T : Any> javaIoSerializable(clazz: KClass<T>): IdeExtrasSerializer<T> {
return IdeJavaIoSerializableExtrasSerializer(clazz)
}
inline fun <reified T : Any> javaIoSerializable(): IdeExtrasSerializer<T> {
return javaIoSerializable(T::class)
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.plugin.ide
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import kotlin.reflect.KClass
internal class IdeJavaIoSerializableExtrasSerializer<T : Any>(
private val clazz: KClass<T>
) : IdeExtrasSerializer<T> {
override fun serialize(context: IdeSerializationContext, value: T): ByteArray? {
return try {
ByteArrayOutputStream().use { byteArrayOutputStream ->
ObjectOutputStream(byteArrayOutputStream).use { oos -> oos.writeObject(value) }
byteArrayOutputStream.toByteArray()
}
} catch (t: Throwable) {
context.logger.error("${ErrorMessages.SERIALIZATION_FAILURE} $value", t)
null
}
}
override fun deserialize(context: IdeSerializationContext, data: ByteArray): T? {
return try {
ObjectInputStream(ByteArrayInputStream(data)).use { stream -> clazz.java.cast(stream.readObject()) }
} catch (t: Throwable) {
context.logger.error("${ErrorMessages.DESERIALIZATION_FAILURE} ${clazz.java}", t)
null
}
}
object ErrorMessages {
const val SERIALIZATION_FAILURE = "Failed to serialize"
const val DESERIALIZATION_FAILURE = "Failed to deserialize"
}
}
@@ -0,0 +1,150 @@
/*
* 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.plugin.ide
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.DependencyResolutionLevel.Default
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.DependencyResolutionLevel.Overwrite
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.SourceSetConstraint
import org.jetbrains.kotlin.gradle.targets.metadata.isNativeSourceSet
import org.jetbrains.kotlin.gradle.utils.getOrPut
import org.jetbrains.kotlin.tooling.core.Extras
interface IdeMultiplatformImport {
fun resolveDependencies(sourceSet: KotlinSourceSet): Set<IdeDependency>
fun <T : Any> serialize(key: Extras.Key<T>, value: T): ByteArray?
@ExternalKotlinTargetApi
fun registerDependencyResolver(
resolver: IdeDependencyResolver,
constraint: SourceSetConstraint,
phase: DependencyResolutionPhase,
level: DependencyResolutionLevel = Default,
)
@ExternalKotlinTargetApi
fun registerDependencyTransformer(
transformer: IdeDependencyTransformer,
constraint: SourceSetConstraint,
phase: DependencyTransformationPhase
)
@ExternalKotlinTargetApi
fun registerDependencyEffect(
effect: IdeDependencyEffect,
constraint: SourceSetConstraint
)
@ExternalKotlinTargetApi
fun registerExtrasSerializationExtension(
extension: IdeExtrasSerializationExtension
)
/**
* Any [IdeDependencyResolver] has to be registered for a given dependency resolution phase in which it participates
* The resolution phases will be executed in the order of their enum's ordinal.
*/
enum class DependencyResolutionPhase {
/**
* Generic phase intended to run before all other resolvers
*/
PreDependencyResolution,
/**
* Resolution phase intended to resolve project to project (source) dependencies
*/
SourceDependencyResolution,
/**
* Resolution phase intended to resolve binary dependencies (downloading and transforming from repositories)
*/
BinaryDependencyResolution,
/**
* Generic phase intended to run after all other resolvers.
*/
PostDependencyResolution
}
/**
* Any [IdeDependencyResolver] has to be registered specifying a certain resolution level.
* Generally, all resolvers registered in a given resolution level will work collaboratively, meaning the dependency resolution
* result is the aggregation of all resolvers running.
*
* However, only the resolvers in the highest resolution result will run e.g.
* If resolvers with level [Overwrite] are found, then only those will contribute to the dependency resolution.
* Otherwise, all [Default] resolvers will run.
*/
enum class DependencyResolutionLevel {
Default, Overwrite
}
/**
* Any [IdeDependencyResolver] has to be registered for a given transformation phase.
* The phases will be executed in the order of this enums ordinal.
*/
enum class DependencyTransformationPhase {
/**
* Generic dependency transformation phase, intended to run a transformation before all other transformers.
*/
PreDependencyTransformationPhase,
/**
* Dependency transformation phase that is free entirely free in its transformation type.
* Note: Adding dependencies to the resolution result might most likely better be modelled as [IdeDependencyResolver]
*/
FreeDependencyTransformationPhase,
/**
* Special dependency transformation phase intended for filtering dependencies.
* This phase is guaranteed to run after the [FreeDependencyTransformationPhase] alongside other dependency filtering
* transformations. Adding filters here is the safest.
*/
DependencyFilteringPhase,
/**
* Generic dependency transformation phase, intended to run a transformation after all other transformers
*/
PostDependencyTransformationPhase
}
/**
* Used for scoping [IdeDependencyResolver], [IdeDependencyResolver] and [IdeDependencyEffect]
*/
fun interface SourceSetConstraint {
operator fun invoke(sourceSet: KotlinSourceSet): Boolean
companion object {
val unconstrained = SourceSetConstraint { true }
val isNative = SourceSetConstraint { sourceSet -> isNativeSourceSet(sourceSet) }
}
}
companion object {
@JvmStatic
fun instance(project: Project): IdeMultiplatformImport {
return project.extraProperties.getOrPut(IdeMultiplatformImport::class.java.name) {
IdeMultiplatformImport(project.multiplatformExtension)
}
}
}
}
internal val Project.kotlinIdeMultiplatformImport: IdeMultiplatformImport get() = IdeMultiplatformImport.instance(project)
@ExternalKotlinTargetApi
fun IdeMultiplatformImport.registerExtrasSerializationExtension(
builder: IdeExtrasSerializationExtensionBuilder.() -> Unit
) {
registerExtrasSerializationExtension(IdeExtrasSerializationExtension(builder))
}
@@ -0,0 +1,26 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.kpm.idea.kotlinDebugKey
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeDependsOnDependencyResolver
fun IdeMultiplatformImport(extension: KotlinMultiplatformExtension): IdeMultiplatformImport {
return IdeMultiplatformImportImpl(extension).apply {
registerDependencyResolver(
resolver = IdeDependsOnDependencyResolver,
constraint = IdeMultiplatformImport.SourceSetConstraint.unconstrained,
phase = IdeMultiplatformImport.DependencyResolutionPhase.SourceDependencyResolution,
level = IdeMultiplatformImport.DependencyResolutionLevel.Default
)
registerExtrasSerializationExtension {
register(kotlinDebugKey, IdeExtrasSerializer.javaIoSerializable())
}
}
}
@@ -0,0 +1,132 @@
/*
* 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.plugin.ide
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.*
import org.jetbrains.kotlin.tooling.core.Extras
internal class IdeMultiplatformImportImpl(
private val extension: KotlinMultiplatformExtension
) : IdeMultiplatformImport {
override fun resolveDependencies(sourceSet: KotlinSourceSet): Set<IdeDependency> {
return createDependencyResolver().resolve(sourceSet)
}
override fun <T : Any> serialize(key: Extras.Key<T>, value: T): ByteArray? {
return null
}
private data class RegisteredDependencyResolver(
val resolver: IdeDependencyResolver,
val constraint: SourceSetConstraint,
val phase: DependencyResolutionPhase,
val level: DependencyResolutionLevel,
)
private data class RegisteredDependencyTransformer(
val transformer: IdeDependencyTransformer,
val constraint: SourceSetConstraint,
val phase: DependencyTransformationPhase
)
private data class RegisteredDependencyEffect(
val effect: IdeDependencyEffect,
val constraint: SourceSetConstraint,
)
private val registeredDependencyResolvers = mutableListOf<RegisteredDependencyResolver>()
private val registeredDependencyTransformers = mutableListOf<RegisteredDependencyTransformer>()
private val registeredDependencyEffects = mutableListOf<RegisteredDependencyEffect>()
private val registeredExtrasSerializationExtensions = mutableListOf<IdeExtrasSerializationExtension>()
@ExternalKotlinTargetApi
override fun registerDependencyResolver(
resolver: IdeDependencyResolver,
constraint: SourceSetConstraint,
phase: DependencyResolutionPhase,
level: DependencyResolutionLevel
) {
registeredDependencyResolvers.add(
RegisteredDependencyResolver(resolver, constraint, phase, level)
)
}
@ExternalKotlinTargetApi
override fun registerDependencyTransformer(
transformer: IdeDependencyTransformer,
constraint: SourceSetConstraint,
phase: DependencyTransformationPhase
) {
registeredDependencyTransformers.add(
RegisteredDependencyTransformer(transformer, constraint, phase)
)
}
@ExternalKotlinTargetApi
override fun registerDependencyEffect(effect: IdeDependencyEffect, constraint: SourceSetConstraint) {
registeredDependencyEffects.add(
RegisteredDependencyEffect(effect, constraint)
)
}
@ExternalKotlinTargetApi
override fun registerExtrasSerializationExtension(extension: IdeExtrasSerializationExtension) {
registeredExtrasSerializationExtensions.add(extension)
}
private fun createDependencyResolver(): IdeDependencyResolver {
return IdeDependencyResolver(DependencyResolutionPhase.values().map { phase ->
createDependencyResolver(phase)
}).withTransformer(createDependencyTransformer())
.withEffect(createDependencyEffect())
}
private fun createDependencyResolver(phase: DependencyResolutionPhase) = IdeDependencyResolver resolve@{ sourceSet ->
val applicableResolvers = registeredDependencyResolvers
.filter { it.phase == phase }
.filter { it.constraint(sourceSet) }
.groupBy { it.level }
/* Find resolvers in the highest resolution level and only consider those */
DependencyResolutionLevel.values().reversed().forEach { level ->
val resolvers = applicableResolvers[level].orEmpty().map { it.resolver }
if (resolvers.isNotEmpty()) {
return@resolve IdeDependencyResolver(resolvers).resolve(sourceSet)
}
}
/* No resolvers found */
emptySet()
}
private fun createDependencyTransformer(): IdeDependencyTransformer {
return IdeDependencyTransformer(DependencyTransformationPhase.values().map { phase ->
createDependencyTransformer(phase)
})
}
private fun createDependencyTransformer(phase: DependencyTransformationPhase): IdeDependencyTransformer {
return IdeDependencyTransformer { sourceSet, dependencies ->
IdeDependencyTransformer(
registeredDependencyTransformers
.filter { it.phase == phase }
.filter { it.constraint(sourceSet) }
.map { it.transformer }
).transform(sourceSet, dependencies)
}
}
private fun createDependencyEffect(): IdeDependencyEffect = IdeDependencyEffect { sourceSet, dependencies ->
registeredDependencyEffects
.filter { it.constraint(sourceSet) }
.forEach { it.effect(sourceSet, dependencies) }
}
}
@@ -0,0 +1,11 @@
/*
* 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.plugin.ide
interface IdeSerializationContext {
val logger: IdeSerializationLogger
val extrasSerializationExtension: IdeExtrasSerializationExtension
}
@@ -0,0 +1,21 @@
/*
* 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.plugin.ide
interface IdeSerializationLogger {
enum class Severity {
WARNING,
ERROR,
}
fun report(severity: Severity, message: String, cause: Throwable? = null)
fun warn(message: String, cause: Throwable? = null) = report(Severity.WARNING, message, cause)
fun error(message: String, cause: Throwable? = null) = report(Severity.ERROR, message, cause)
object None : IdeSerializationLogger {
override fun report(severity: Severity, message: String, cause: Throwable?) = Unit
}
}
@@ -0,0 +1,32 @@
/*
* 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.plugin.ide.dependencyResolvers
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependency
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.IdeSourceCoordinates
import org.jetbrains.kotlin.gradle.plugin.ide.IdeSourceDependency
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.currentBuildId
import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.tooling.core.emptyExtras
internal object IdeDependsOnDependencyResolver : IdeDependencyResolver {
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeDependency> {
return sourceSet.internal.dependsOnClosure.map { dependsOnSourceSet ->
IdeSourceDependency(
type = IdeSourceDependency.Type.DependsOn,
coordinates = IdeSourceCoordinates(
buildId = dependsOnSourceSet.internal.project.currentBuildId().name,
projectPath = dependsOnSourceSet.internal.project.path,
projectName = dependsOnSourceSet.internal.project.name,
sourceSetName = dependsOnSourceSet.name
),
extras = emptyExtras()
)
}.toSet()
}
}
@@ -11,6 +11,7 @@ import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
@@ -24,8 +25,9 @@ import javax.inject.Inject
const val METADATA_CONFIGURATION_NAME_SUFFIX = "DependenciesMetadata"
@InternalKotlinGradlePluginApi
abstract class DefaultKotlinSourceSet @Inject constructor(
private val project: Project,
final override val project: Project,
val displayName: String
) : AbstractKotlinSourceSet() {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.plugin.sources
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.utils.MutableObservableSet
@@ -16,6 +17,7 @@ internal val KotlinSourceSet.internal: InternalKotlinSourceSet
)
internal interface InternalKotlinSourceSet : KotlinSourceSet {
val project: Project
override val dependsOn: ObservableSet<KotlinSourceSet>
val dependsOnClosure: ObservableSet<KotlinSourceSet>
val withDependsOnClosure: ObservableSet<KotlinSourceSet>
@@ -0,0 +1,63 @@
/*
* 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.ide
import org.jetbrains.kotlin.gradle.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.ide.IdeSourceDependency
import org.jetbrains.kotlin.gradle.plugin.ide.kotlinIdeMultiplatformImport
import kotlin.test.Test
class IdeMultiplatformResolveDependsOnDependenciesTest {
@Test
fun `test - sample 0 - default dependsOn to commonMain and commonTest`() {
val project = buildProjectWithMPP()
val kotlin = project.multiplatformExtension
kotlin.jvm()
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val jvmMain = kotlin.sourceSets.getByName("jvmMain")
val jvmTest = kotlin.sourceSets.getByName("jvmTest")
project.kotlinIdeMultiplatformImport.resolveDependencies(jvmMain)
.filterIsInstance<IdeSourceDependency>()
.assertMatches(dependsOnDependency(commonMain))
project.kotlinIdeMultiplatformImport.resolveDependencies(jvmTest)
.filterIsInstance<IdeSourceDependency>()
.assertMatches(dependsOnDependency(commonTest))
}
@Test
fun `test - sample 1 - custom dependsOn edge`() {
val project = buildProjectWithMPP()
val kotlin = project.multiplatformExtension
kotlin.jvm()
val commonMain = kotlin.sourceSets.getByName("commonMain")
val customMain = kotlin.sourceSets.create("customMain")
val jvmMain = kotlin.sourceSets.getByName("jvmMain")
jvmMain.dependsOn(customMain)
customMain.dependsOn(commonMain)
project.kotlinIdeMultiplatformImport.resolveDependencies(jvmMain)
.filterIsInstance<IdeSourceDependency>()
.assertMatches(
dependsOnDependency(commonMain),
dependsOnDependency(customMain)
)
project.kotlinIdeMultiplatformImport.resolveDependencies(customMain)
.filterIsInstance<IdeSourceDependency>()
.assertMatches(dependsOnDependency(commonMain))
}
}
@@ -0,0 +1,91 @@
/*
* 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.ide
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependency
import org.jetbrains.kotlin.gradle.plugin.ide.IdeSourceDependency
import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.junit.jupiter.api.fail
fun buildIdeDependencyMatchers(notation: Any?): List<IdeDependencyMatcher> {
return when (notation) {
null -> return emptyList()
is IdeDependencyMatcher -> listOf(notation)
is Iterable<*> -> notation.flatMap { buildIdeDependencyMatchers(it) }
else -> error("Can't build ${IdeSourceDependencyMatcher::class.java.simpleName} from $notation")
}
}
fun ideSourceDependency(type: IdeSourceDependency.Type, project: Project, sourceSetName: String): IdeDependencyMatcher {
return IdeSourceDependencyMatcher(type, project.path, sourceSetName)
}
fun dependsOnDependency(project: Project, sourceSetName: String) =
ideSourceDependency(IdeSourceDependency.Type.DependsOn, project, sourceSetName)
fun dependsOnDependency(sourceSet: KotlinSourceSet) =
dependsOnDependency(sourceSet.internal.project, sourceSet.name)
interface IdeDependencyMatcher {
val description: String
fun matches(dependency: IdeDependency): Boolean
}
private class IdeSourceDependencyMatcher(
val type: IdeSourceDependency.Type,
val projectPath: String,
val sourceSetName: String
) : IdeDependencyMatcher {
override val description: String =
"source($type)::$projectPath/$sourceSetName"
override fun matches(dependency: IdeDependency): Boolean {
if (dependency !is IdeSourceDependency) return false
return dependency.type == type &&
dependency.coordinates.projectPath == projectPath &&
dependency.coordinates.sourceSetName == sourceSetName
}
}
fun Iterable<IdeDependency>.assertMatches(vararg notation: Any?) {
val thisList = toList()
val matchers = notation.flatMap { buildIdeDependencyMatchers(it) }
val unexpectedDependencies = thisList.filter { dependency -> matchers.none { matcher -> matcher.matches(dependency) } }
val missingDependencies = matchers.filter { matcher -> thisList.none { dependency -> matcher.matches(dependency) } }
if (unexpectedDependencies.isEmpty() && missingDependencies.isEmpty()) {
return
}
fail(
buildString {
if (unexpectedDependencies.isNotEmpty()) {
appendLine()
appendLine("Unexpected dependency found:")
unexpectedDependencies.forEach { unexpectedDependency ->
appendLine("\"${unexpectedDependency}\",")
}
}
if (missingDependencies.isNotEmpty()) {
appendLine()
appendLine("Missing dependencies:")
missingDependencies.forEach { missingDependency ->
appendLine(missingDependency.description)
}
}
appendLine()
appendLine("Dependencies:")
thisList.forEach { dependency ->
appendLine("\"${dependency}\",")
}
}
)
}