Re-structure KotlinMPPGradleProjectResolver.populateModuleDependencies

This commit is contained in:
Yaroslav Chernyshev
2021-03-11 22:30:12 +03:00
parent 18a06dc37e
commit 960a7dca45
20 changed files with 1445 additions and 384 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.configuration
import com.google.common.graph.GraphBuilder import com.google.common.graph.GraphBuilder
import com.google.common.graph.Graphs import com.google.common.graph.Graphs
import com.intellij.build.events.MessageEvent
import com.intellij.ide.plugins.PluginManager import com.intellij.ide.plugins.PluginManager
import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.DataNode
@@ -39,10 +40,17 @@ import org.jetbrains.kotlin.config.ExternalSystemRunTask
import org.jetbrains.kotlin.config.ExternalSystemTestRunTask import org.jetbrains.kotlin.config.ExternalSystemTestRunTask
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.idea.PlatformVersion
import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_NOT_IMPORTED_COMMON_SOURCE_SETS_SETTING import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_NOT_IMPORTED_COMMON_SOURCE_SETS_SETTING
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesDependencySubstitutor import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesDependencySubstitutor
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesFixer import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesFixer
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE
import org.jetbrains.kotlin.idea.configuration.mpp.createPopulateModuleDependenciesContext
import org.jetbrains.kotlin.idea.configuration.mpp.getCompilations
import org.jetbrains.kotlin.idea.configuration.mpp.populateModuleDependenciesByCompilations
import org.jetbrains.kotlin.idea.configuration.mpp.populateModuleDependenciesBySourceSetVisibilityGraph
import org.jetbrains.kotlin.idea.configuration.utils.UnsafeTestSourceSetHeuristicApi
import org.jetbrains.kotlin.idea.configuration.utils.predictedProductionSourceSetName
import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling
import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty
import org.jetbrains.kotlin.util.removeSuffixIfPresent import org.jetbrains.kotlin.util.removeSuffixIfPresent
@@ -94,7 +102,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
} }
override fun populateModuleContentRoots(gradleModule: IdeaModule, ideModule: DataNode<ModuleData>) { override fun populateModuleContentRoots(gradleModule: IdeaModule, ideModule: DataNode<ModuleData>) {
val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) val mppModel = resolverCtx.getMppModel(gradleModule)
if (mppModel == null) { if (mppModel == null) {
return super.populateModuleContentRoots(gradleModule, ideModule) return super.populateModuleContentRoots(gradleModule, ideModule)
} else { } else {
@@ -117,7 +125,9 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
val projectDataNode = ideModule.getDataNode(ProjectKeys.PROJECT)!! val projectDataNode = ideModule.getDataNode(ProjectKeys.PROJECT)!!
val moduleOutputsMap = projectDataNode.getUserData(MODULES_OUTPUTS)!! val moduleOutputsMap = projectDataNode.getUserData(MODULES_OUTPUTS)!!
val outputDirs = HashSet<String>() val outputDirs = HashSet<String>()
processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation -> getCompilations(gradleModule, mppModel, ideModule, resolverCtx)
.filterNot { (_, compilation) -> delegateToAndroidPlugin(compilation) }
.forEach { (dataNode, compilation) ->
var gradleOutputMap = dataNode.getUserData(GradleProjectResolver.GRADLE_OUTPUTS) var gradleOutputMap = dataNode.getUserData(GradleProjectResolver.GRADLE_OUTPUTS)
if (gradleOutputMap == null) { if (gradleOutputMap == null) {
gradleOutputMap = MultiMap.create() gradleOutputMap = MultiMap.create()
@@ -228,7 +238,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
} }
} }
private fun Collection<ExternalDependency>.modifyDependenciesOnMppModules( internal fun Collection<ExternalDependency>.modifyDependenciesOnMppModules(
ideProject: DataNode<ProjectData>, ideProject: DataNode<ProjectData>,
resolverCtx: ProjectResolverContext resolverCtx: ProjectResolverContext
) { ) {
@@ -289,8 +299,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
val path = toCanonicalPath(target.jar!!.archiveFile!!.absolutePath) val path = toCanonicalPath(target.jar!!.archiveFile!!.absolutePath)
val currentModules = userData[path] ?: ArrayList<String>().apply { userData[path] = this } val currentModules = userData[path] ?: ArrayList<String>().apply { userData[path] = this }
// Test modules should not be added. Otherwise we could get dependnecy of java.mail on jvmTest // Test modules should not be added. Otherwise we could get dependnecy of java.mail on jvmTest
val allSourceSets = target.compilations.filter { !it.isTestModule }.flatMap { it.allSourceSets }.toSet() val allSourceSets = target.compilations.filter { !it.isTestModule }.flatMap { it.declaredSourceSets }.toSet()
val availableViaDependsOn = allSourceSets.flatMap { it.dependsOnSourceSets }.mapNotNull { mppModel.sourceSets[it] } val availableViaDependsOn = allSourceSets.flatMap { it.allDependsOnSourceSets }.mapNotNull { mppModel.sourceSetsByName[it] }
allSourceSets.union(availableViaDependsOn).forEach { sourceSet -> allSourceSets.union(availableViaDependsOn).forEach { sourceSet ->
currentModules.add(getKotlinModuleId(gradleModule, sourceSet, resolverCtx)) currentModules.add(getKotlinModuleId(gradleModule, sourceSet, resolverCtx))
} }
@@ -362,7 +372,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
resolverCtx resolverCtx
) ?: continue ) ?: continue
kotlinSourceSet.externalSystemRunTasks = kotlinSourceSet.externalSystemRunTasks =
compilation.allSourceSets.firstNotNullResult { sourceSetToRunTasks[it] } ?: emptyList() compilation.declaredSourceSets.firstNotNullResult { sourceSetToRunTasks[it] } ?: emptyList()
if (compilation.platform == KotlinPlatform.JVM || compilation.platform == KotlinPlatform.ANDROID) { if (compilation.platform == KotlinPlatform.JVM || compilation.platform == KotlinPlatform.ANDROID) {
compilationData.targetCompatibility = (kotlinSourceSet.compilerArguments as? K2JVMCompilerArguments)?.jvmTarget compilationData.targetCompatibility = (kotlinSourceSet.compilerArguments as? K2JVMCompilerArguments)?.jvmTarget
@@ -374,9 +384,9 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
} }
} }
for (sourceSet in compilation.allSourceSets) { for (sourceSet in compilation.declaredSourceSets) {
sourceSetToCompilationData.getOrPut(sourceSet.name) { LinkedHashSet() } += compilationData sourceSetToCompilationData.getOrPut(sourceSet.name) { LinkedHashSet() } += compilationData
for (dependentSourceSetName in sourceSet.dependsOnSourceSets) { for (dependentSourceSetName in sourceSet.allDependsOnSourceSets) {
sourceSetToCompilationData.getOrPut(dependentSourceSetName) { LinkedHashSet() } += compilationData sourceSetToCompilationData.getOrPut(dependentSourceSetName) { LinkedHashSet() } += compilationData
} }
} }
@@ -394,9 +404,9 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
} }
val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() }
for (sourceSet in mppModel.sourceSets.values) { for (sourceSet in mppModel.sourceSetsByName.values) {
if (delegateToAndroidPlugin(sourceSet)) continue if (delegateToAndroidPlugin(sourceSet)) continue
if (sourceSet.actualPlatforms.supports(KotlinPlatform.COMMON) && ignoreCommonSourceSets) continue if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.COMMON && ignoreCommonSourceSets) continue
val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx)
val existingSourceSetDataNode = sourceSetMap[moduleId]?.first val existingSourceSetDataNode = sourceSetMap[moduleId]?.first
if (existingSourceSetDataNode?.kotlinSourceSet != null) continue if (existingSourceSetDataNode?.kotlinSourceSet != null) continue
@@ -410,15 +420,14 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
it.group = externalProject.group it.group = externalProject.group
it.version = externalProject.version it.version = externalProject.version
val name = sourceSet.name if (sourceSet.isTestModule) {
val baseName = name.removeSuffix("Test")
if (baseName != name) {
it.productionModuleId = getInternalModuleName( it.productionModuleId = getInternalModuleName(
gradleModule, gradleModule,
externalProject, externalProject,
sourceSet, sourceSet,
resolverCtx, resolverCtx,
baseName + "Main" @OptIn(UnsafeTestSourceSetHeuristicApi::class)
predictedProductionSourceSetName(sourceSet.name)
) )
} }
@@ -430,7 +439,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
.mapNotNull { compilationData -> compilationData.targetCompatibility } .mapNotNull { compilationData -> compilationData.targetCompatibility }
.minWithOrNull(VersionComparatorUtil.COMPARATOR) .minWithOrNull(VersionComparatorUtil.COMPARATOR)
if (sourceSet.actualPlatforms.getSinglePlatform() == KotlinPlatform.NATIVE) { if (sourceSet.actualPlatforms.singleOrNull() == KotlinPlatform.NATIVE) {
it.konanTargets = compilationDataRecords it.konanTargets = compilationDataRecords
.flatMap { compilationData -> compilationData.konanTargets } .flatMap { compilationData -> compilationData.konanTargets }
.toSet() .toSet()
@@ -438,7 +447,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
} }
} }
val kotlinSourceSet = createSourceSetInfo(sourceSet, gradleModule, resolverCtx) ?: continue val kotlinSourceSet = createSourceSetInfo(mppModel, sourceSet, gradleModule, resolverCtx) ?: continue
kotlinSourceSet.externalSystemRunTasks = sourceSetToRunTasks[sourceSet] ?: emptyList() kotlinSourceSet.externalSystemRunTasks = sourceSetToRunTasks[sourceSet] ?: emptyList()
val sourceSetDataNode = val sourceSetDataNode =
@@ -495,15 +504,15 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
) )
} }
val allRunTasks = testRunTasks + nativeMainRunTasks val allRunTasks = testRunTasks + nativeMainRunTasks
compilation.allSourceSets.forEach { sourceSet -> compilation.declaredSourceSets.forEach { sourceSet ->
sourceSetToRunTasks.getOrPut(sourceSet) { LinkedHashSet() } += allRunTasks sourceSetToRunTasks.getOrPut(sourceSet) { LinkedHashSet() } += allRunTasks
sourceSet.dependsOnSourceSets.forEach { dependentModule -> mppModel.resolveAllDependsOnSourceSets(sourceSet).forEach { dependentModule ->
dependsOnReverseGraph.getOrPut(dependentModule) { LinkedHashSet() } += sourceSet dependsOnReverseGraph.getOrPut(dependentModule.name) { LinkedHashSet() } += sourceSet
} }
} }
} }
} }
mppModel.sourceSets.forEach { (sourceSetName, sourceSet) -> mppModel.sourceSetsByName.forEach { (sourceSetName, sourceSet) ->
dependsOnReverseGraph[sourceSetName]?.forEach { dependingSourceSet -> dependsOnReverseGraph[sourceSetName]?.forEach { dependingSourceSet ->
sourceSetToRunTasks.getOrPut(sourceSet) { LinkedHashSet() } += sourceSetToRunTasks[dependingSourceSet] ?: emptyList() sourceSetToRunTasks.getOrPut(sourceSet) { LinkedHashSet() } += sourceSetToRunTasks[dependingSourceSet] ?: emptyList()
} }
@@ -519,7 +528,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
val mppModel = resolverCtx.getMppModel(gradleModule) ?: return val mppModel = resolverCtx.getMppModel(gradleModule) ?: return
val sourceSetToPackagePrefix = mppModel.targets.flatMap { it.compilations } val sourceSetToPackagePrefix = mppModel.targets.flatMap { it.compilations }
.flatMap { compilation -> .flatMap { compilation ->
compilation.allSourceSets.map { sourceSet -> sourceSet.name to compilation.kotlinTaskProperties.packagePrefix } compilation.declaredSourceSets.map { sourceSet -> sourceSet.name to compilation.kotlinTaskProperties.packagePrefix }
} }
.toMap() .toMap()
if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return
@@ -563,7 +572,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
mppModelPureKotlinSourceFolders + ideModule.pureKotlinSourceFolders mppModelPureKotlinSourceFolders + ideModule.pureKotlinSourceFolders
} }
private data class CompilationWithDependencies( internal data class CompilationWithDependencies(
val compilation: KotlinCompilation, val compilation: KotlinCompilation,
val substitutedDependencies: List<ExternalDependency> val substitutedDependencies: List<ExternalDependency>
) { ) {
@@ -581,275 +590,17 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
ideModule: DataNode<ModuleData>, ideModule: DataNode<ModuleData>,
resolverCtx: ProjectResolverContext resolverCtx: ProjectResolverContext
) { ) {
val mppModel = resolverCtx.getMppModel(gradleModule) ?: return val context = createPopulateModuleDependenciesContext(
mppModel.dependencyMap.values.modifyDependenciesOnMppModules(ideProject, resolverCtx) gradleModule = gradleModule,
val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return ideProject = ideProject,
val artifactsMap = ideProject.getUserData(CONFIGURATION_ARTIFACTS) ?: return ideModule = ideModule,
val substitutor = KotlinNativeLibrariesDependencySubstitutor(mppModel, gradleModule, resolverCtx) resolverCtx = resolverCtx
val sourceSetToCompilations = mutableMapOf<String, MutableList<CompilationWithDependencies>>() ) ?: return
val processedModuleIds = HashSet<String>() populateModuleDependenciesByCompilations(context)
processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation -> populateModuleDependenciesBySourceSetVisibilityGraph(context)
if (processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx))) {
val substitutedDependencies =
substitutor.substituteDependencies(compilation.dependencies.mapNotNull { mppModel.dependencyMap[it] })
buildDependencies(
resolverCtx,
sourceSetMap,
artifactsMap,
dataNode,
preprocessDependencies(substitutedDependencies),
ideProject
)
KotlinNativeLibrariesFixer.applyTo(dataNode, ideProject)
for (sourceSet in compilation.allSourceSets) {
(sourceSet.dependsOnSourceSets + sourceSet.name).forEach {
sourceSetToCompilations
.getOrPut(it) { mutableListOf() }
.add(CompilationWithDependencies(compilation, substitutedDependencies))
}
if (sourceSet.fullName() == compilation.fullName()) continue
val targetDataNode = getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx) ?: continue
addDependency(dataNode, targetDataNode, sourceSet.isTestModule)
}
}
}
val sourceSetGraph = GraphBuilder.directed().build<KotlinSourceSet>()
processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet ->
sourceSetGraph.addNode(sourceSet)
val productionSourceSet = dataNode
?.data
?.productionModuleId
?.let { ideModule.findChildModuleByInternalName(it) }
?.kotlinSourceSet
?.kotlinModule
?.toSourceSet(mppModel)
if (productionSourceSet != null) {
sourceSetGraph.putEdge(sourceSet, productionSourceSet)
}
for (targetSourceSetName in sourceSet.dependsOnSourceSets) {
val targetSourceSet = mppModel.sourceSets[targetSourceSetName] ?: continue
sourceSetGraph.putEdge(sourceSet, targetSourceSet)
}
// Workaround: Non-android source sets have commonMain/commonTest in their dependsOn
// Remove when the same is implemented for Android modules as well
if (sourceSet.actualPlatforms.supports(KotlinPlatform.ANDROID)) {
val commonSourceSetName = if (sourceSet.isTestModule) {
KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME
} else {
KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME
}
val commonSourceSet = mppModel.sourceSets[commonSourceSetName]
if (commonSourceSet != null && commonSourceSet != sourceSet) {
sourceSetGraph.putEdge(sourceSet, commonSourceSet)
}
}
}
val closedSourceSetGraph = Graphs.transitiveClosure(sourceSetGraph)
for (sourceSet in closedSourceSetGraph.nodes()) {
val isAndroid = delegateToAndroidPlugin(sourceSet)
val fromDataNode = if (isAndroid) {
ideModule
} else {
getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx)
} ?: continue
val dependeeSourceSets = closedSourceSetGraph.successors(sourceSet)
val sourceSetInfos = if (isAndroid) {
ideModule.kotlinAndroidSourceSets?.filter {
(it.kotlinModule as? KotlinCompilation)?.allSourceSets?.contains(sourceSet) ?: false
} ?: emptyList()
} else {
listOfNotNull(fromDataNode.kotlinSourceSet)
}
for (sourceSetInfo in sourceSetInfos) {
if (sourceSetInfo.kotlinModule is KotlinCompilation) {
val selfName = sourceSetInfo.kotlinModule.fullName()
sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx)
}
}
if (delegateToAndroidPlugin(sourceSet)) continue
for (dependeeSourceSet in dependeeSourceSets) {
val toDataNode = getSiblingKotlinModuleData(dependeeSourceSet, gradleModule, ideModule, resolverCtx) ?: continue
addDependency(fromDataNode, toDataNode, dependeeSourceSet.isTestModule)
}
if (processedModuleIds.add(getKotlinModuleId(gradleModule, sourceSet, resolverCtx))) {
val mergedSubstitutedDependencies = LinkedHashSet<KotlinDependency>().apply {
val forceNativeDependencyPropagation: Boolean
val excludeInheritedNativeDependencies: Boolean
if (mppModel.extraFeatures.isHMPPEnabled && sourceSet.actualPlatforms.getSinglePlatform() == KotlinPlatform.NATIVE) {
forceNativeDependencyPropagation = mppModel.extraFeatures.isNativeDependencyPropagationEnabled
excludeInheritedNativeDependencies = !forceNativeDependencyPropagation
} else {
forceNativeDependencyPropagation = false
excludeInheritedNativeDependencies = false
}
addAll(substitutor.substituteDependencies(sourceSet))
dependeeSourceSets.flatMapTo(this) { dependeeSourceSet ->
substitutor.substituteDependencies(dependeeSourceSet).run {
if (excludeInheritedNativeDependencies)
filter { !it.name.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) }
else this
}
}
if (forceNativeDependencyPropagation) {
sourceSetToCompilations[sourceSet.name]?.let { compilations ->
addAll(propagatedNativeDependencies(compilations))
}
}
if (mppModel.extraFeatures.isHMPPEnabled) {
addAll(propagatedPlatformDependencies(mppModel, sourceSet))
}
}
buildDependencies(
resolverCtx,
sourceSetMap,
artifactsMap,
fromDataNode,
preprocessDependencies(mergedSubstitutedDependencies),
ideProject
)
@Suppress("UNCHECKED_CAST")
KotlinNativeLibrariesFixer.applyTo(fromDataNode as DataNode<GradleSourceSetData>, ideProject)
}
}
} }
private fun KotlinNativeLibrariesDependencySubstitutor.substituteDependencies(kotlinModule: KotlinModule): List<ExternalDependency> = internal fun getSiblingKotlinModuleData(
substituteDependencies(kotlinModule.dependencies.mapNotNull { mppModel.dependencyMap[it] })
// We can't really commonize native platform libraries yet.
// But APIs for different targets may be very similar.
// E.g. ios_arm64 and ios_x64 have almost identical platform libraries.
// We handle these special cases and resolve common sources for such
// targets against libraries of one of them. E.g. common sources for
// ios_x64 and ios_arm64 will be resolved against ios_arm64 libraries.
//
// Currently such special casing is available for Apple platforms
// (iOS, watchOS and tvOS) and native Android (ARM, X86).
// TODO: Do we need to support user's interop libraries too?
private fun propagatedNativeDependencies(compilations: List<CompilationWithDependencies>): List<ExternalDependency> {
if (compilations.size <= 1) {
return emptyList()
}
val copyFrom = when {
compilations.all { it.isAppleCompilation } ->
compilations.selectFirstAvailableTarget(
"watchos_arm64", "watchos_arm32", "watchos_x64", "watchos_x86",
"ios_arm64", "ios_arm32", "ios_x64",
"tvos_arm64", "tvos_x64"
)
compilations.all { it.konanTarget?.startsWith("android") == true } ->
compilations.selectFirstAvailableTarget(
"android_arm64", "android_arm32", "android_x64", "android_x86"
)
else -> return emptyList()
}
return copyFrom.dependencyNames.mapNotNull { (name, dependency) ->
when {
!name.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) -> null // Support only default platform libs for now.
compilations.all { it.dependencyNames.containsKey(name) } -> dependency
else -> null
}
}
}
/**
* Source sets sharing code between JVM and Android are the only intermediate source sets that
* can effectively consume a dependency's platform artifact.
* When a library only offers a JVM variant, then Android and JVM consume this variant of the library.
*
* This will be replaced later on by [KT-43450](https://youtrack.jetbrains.com/issue/KT-43450)
*
* @return all dependencies being present across given JVM and Android compilations that this [sourceSet] can also participates in.
*/
private fun propagatedPlatformDependencies(
mppModel: KotlinMPPGradleModel,
sourceSet: KotlinSourceSet,
): Set<ExternalDependency> {
if (
sourceSet.actualPlatforms.platforms.sorted() == listOf(KotlinPlatform.JVM, KotlinPlatform.ANDROID).sorted()
) {
return mppModel.targets
.filter { target -> target.platform == KotlinPlatform.JVM || target.platform == KotlinPlatform.ANDROID }
.flatMap { target -> target.compilations }
.filter { compilation -> compilation.dependsOnSourceSet(mppModel, sourceSet) }
.map { targetCompilations -> targetCompilations.dependencies.mapNotNull(mppModel.dependencyMap::get).toSet() }
.reduceOrNull { acc, dependencies -> acc.intersect(dependencies) }.orEmpty()
}
return emptySet()
}
private fun KotlinCompilation.dependsOnSourceSet(mppModel: KotlinMPPGradleModel, sourceSet: KotlinSourceSet): Boolean {
return allSourceSets.any { containedSourceSet -> sourceSet.isOrDependsOnSourceSet(mppModel, containedSourceSet) }
}
private fun KotlinSourceSet.isOrDependsOnSourceSet(mppModel: KotlinMPPGradleModel, sourceSet: KotlinSourceSet): Boolean {
if (this == sourceSet) return true
return this.dependsOnSourceSets
.map { dependencySourceSetName -> mppModel.sourceSets.getValue(dependencySourceSetName) }
.any { dependencySourceSet -> dependencySourceSet.isOrDependsOnSourceSet(mppModel, sourceSet) }
}
private val CompilationWithDependencies.isAppleCompilation: Boolean
get() = konanTarget?.let {
it.startsWith("ios") || it.startsWith("watchos") || it.startsWith("tvos")
} ?: false
private fun Iterable<CompilationWithDependencies>.selectFirstAvailableTarget(@NonNls vararg targetsByPriority: String): CompilationWithDependencies {
for (target in targetsByPriority) {
val result = firstOrNull { it.konanTarget == target }
if (result != null) {
return result
}
}
return first()
}
private fun KotlinModule.toSourceSet(mppModel: KotlinMPPGradleModel) = when (this) {
is KotlinSourceSet -> this
is KotlinCompilation -> mppModel.sourceSets[fullName()]
else -> null
}
private fun preprocessDependencies(dependencies: Collection<KotlinDependency>): List<ExternalDependency> {
return dependencies
.groupBy { it.id }
.mapValues { it.value.firstOrNull { it.scope == "COMPILE" } ?: it.value.lastOrNull() }
.values
.filterNotNull()
}
private fun addDependency(fromModule: DataNode<*>, toModule: DataNode<*>, dependOnTestModule: Boolean) {
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 nodeToModify =
existing.singleOrNull() ?: existing.firstOrNull { it.scope == DependencyScope.COMPILE } ?: existing.firstOrNull()
if (nodeToModify != null) {
nodeToModify.scope = DependencyScope.COMPILE
nodeToModify.isProductionOnTestDependency = nodeToModify.isProductionOnTestDependency || dependOnTestModule
return
}
val moduleDependencyData = ModuleDependencyData(fromData, toData).also {
it.scope = DependencyScope.COMPILE
it.isExported = false
it.isProductionOnTestDependency = dependOnTestModule
}
fromModule.createChild(ProjectKeys.MODULE_DEPENDENCY, moduleDependencyData)
}
private fun getSiblingKotlinModuleData(
kotlinModule: KotlinModule, kotlinModule: KotlinModule,
gradleModule: IdeaModule, gradleModule: IdeaModule,
ideModule: DataNode<ModuleData>, ideModule: DataNode<ModuleData>,
@@ -885,36 +636,13 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
sourceSetsMap[dataNode.data.id] = dataNode sourceSetsMap[dataNode.data.id] = dataNode
} }
} }
for (sourceSet in mppModel.sourceSets.values) { for (sourceSet in mppModel.sourceSetsByName.values) {
val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx)
val moduleDataNode = sourceSetsMap[moduleId] val moduleDataNode = sourceSetsMap[moduleId]
processor(moduleDataNode, sourceSet) processor(moduleDataNode, sourceSet)
} }
} }
private fun processCompilations(
gradleModule: IdeaModule,
mppModel: KotlinMPPGradleModel,
ideModule: DataNode<ModuleData>,
resolverCtx: ProjectResolverContext,
processor: (DataNode<GradleSourceSetData>, KotlinCompilation) -> Unit
) {
val sourceSetsMap = HashMap<String, DataNode<GradleSourceSetData>>()
for (dataNode in ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)) {
if (dataNode.kotlinSourceSet != null) {
sourceSetsMap[dataNode.data.id] = dataNode
}
}
for (target in mppModel.targets) {
if (delegateToAndroidPlugin(target)) continue
for (compilation in target.compilations) {
val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx)
val moduleDataNode = sourceSetsMap[moduleId] ?: continue
processor(moduleDataNode, compilation)
}
}
}
private val IdeaModule.jdkNameIfAny private val IdeaModule.jdkNameIfAny
get() = try { get() = try {
jdkName jdkName
@@ -988,7 +716,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
if (effectiveClassesDir != null) { if (effectiveClassesDir != null) {
sourcesWithTypes += compilation.sourceType to DefaultExternalSourceDirectorySet().also { dirSet -> sourcesWithTypes += compilation.sourceType to DefaultExternalSourceDirectorySet().also { dirSet ->
dirSet.outputDir = effectiveClassesDir dirSet.outputDir = effectiveClassesDir
dirSet.srcDirs = compilation.allSourceSets.flatMapTo(LinkedHashSet()) { it.sourceDirs } dirSet.srcDirs = compilation.declaredSourceSets.flatMapTo(LinkedHashSet()) { it.sourceDirs }
dirSet.gradleOutputDirs += compilation.output.classesDirs dirSet.gradleOutputDirs += compilation.output.classesDirs
dirSet.setInheritedCompilerOutput(false) dirSet.setInheritedCompilerOutput(false)
} }
@@ -996,7 +724,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
if (resourcesDir != null) { if (resourcesDir != null) {
sourcesWithTypes += compilation.resourceType to DefaultExternalSourceDirectorySet().also { dirSet -> sourcesWithTypes += compilation.resourceType to DefaultExternalSourceDirectorySet().also { dirSet ->
dirSet.outputDir = resourcesDir dirSet.outputDir = resourcesDir
dirSet.srcDirs = compilation.allSourceSets.flatMapTo(LinkedHashSet()) { it.resourceDirs } dirSet.srcDirs = compilation.declaredSourceSets.flatMapTo(LinkedHashSet()) { it.resourceDirs }
dirSet.gradleOutputDirs += resourcesDir dirSet.gradleOutputDirs += resourcesDir
dirSet.setInheritedCompilerOutput(false) dirSet.setInheritedCompilerOutput(false)
} }
@@ -1034,7 +762,9 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
private val KotlinModule.resourceType private val KotlinModule.resourceType
get() = if (isTestModule) ExternalSystemSourceType.TEST_RESOURCE else ExternalSystemSourceType.RESOURCE get() = if (isTestModule) ExternalSystemSourceType.TEST_RESOURCE else ExternalSystemSourceType.RESOURCE
@OptIn(ExperimentalGradleToolingApi::class)
private fun createSourceSetInfo( private fun createSourceSetInfo(
mppModel: KotlinMPPGradleModel,
sourceSet: KotlinSourceSet, sourceSet: KotlinSourceSet,
gradleModule: IdeaModule, gradleModule: IdeaModule,
resolverCtx: ProjectResolverContext resolverCtx: ProjectResolverContext
@@ -1044,13 +774,15 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
val languageSettings = sourceSet.languageSettings val languageSettings = sourceSet.languageSettings
info.moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) info.moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx)
info.gradleModuleId = getModuleId(resolverCtx, gradleModule) info.gradleModuleId = getModuleId(resolverCtx, gradleModule)
info.actualPlatforms.addSimplePlatforms(sourceSet.actualPlatforms.platforms) info.actualPlatforms.pushPlatforms(sourceSet.actualPlatforms)
info.isTestModule = sourceSet.isTestModule info.isTestModule = sourceSet.isTestModule
info.dependsOn = sourceSet.dependsOnSourceSets.toList().map { info.dependsOn = mppModel.resolveAllDependsOnSourceSets(sourceSet).map { dependsOnSourceSet ->
getGradleModuleQualifiedName(resolverCtx, gradleModule, it) getGradleModuleQualifiedName(resolverCtx, gradleModule, dependsOnSourceSet.name)
} }
//TODO(auskov): target flours are lost here //TODO(auskov): target flours are lost here
info.compilerArguments = createCompilerArguments(emptyList(), sourceSet.actualPlatforms.getSinglePlatform()).also { info.compilerArguments = createCompilerArguments(
emptyList(), sourceSet.actualPlatforms.singleOrNull() ?: KotlinPlatform.COMMON
).also {
it.multiPlatform = true it.multiPlatform = true
it.languageVersion = languageSettings.languageVersion it.languageVersion = languageSettings.languageVersion
it.apiVersion = languageSettings.apiVersion it.apiVersion = languageSettings.apiVersion
@@ -1086,9 +818,9 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
return KotlinSourceSetInfo(compilation).also { sourceSetInfo -> return KotlinSourceSetInfo(compilation).also { sourceSetInfo ->
sourceSetInfo.moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) sourceSetInfo.moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx)
sourceSetInfo.gradleModuleId = getModuleId(resolverCtx, gradleModule) sourceSetInfo.gradleModuleId = getModuleId(resolverCtx, gradleModule)
sourceSetInfo.actualPlatforms.addSimplePlatforms(listOf(compilation.platform)) sourceSetInfo.actualPlatforms.pushPlatforms(listOf(compilation.platform))
sourceSetInfo.isTestModule = compilation.isTestModule sourceSetInfo.isTestModule = compilation.isTestModule
sourceSetInfo.dependsOn = compilation.allSourceSets.flatMap { it.dependsOnSourceSets }.map { sourceSetInfo.dependsOn = compilation.declaredSourceSets.flatMap { it.allDependsOnSourceSets }.map {
getGradleModuleQualifiedName(resolverCtx, gradleModule, it) getGradleModuleQualifiedName(resolverCtx, gradleModule, it)
}.distinct().toList() }.distinct().toList()
sourceSetInfo.compilerArguments = sourceSetInfo.compilerArguments =
@@ -1105,7 +837,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
/** Checks if our IDE doesn't support such platform */ /** Checks if our IDE doesn't support such platform */
private fun KotlinPlatform.isNotSupported() = IdePlatformKindTooling.getToolingIfAny(this) == null private fun KotlinPlatform.isNotSupported() = IdePlatformKindTooling.getToolingIfAny(this) == null
private fun KotlinSourceSetInfo.addSourceSets( internal fun KotlinSourceSetInfo.addSourceSets(
sourceSets: Collection<KotlinModule>, sourceSets: Collection<KotlinModule>,
selfName: String, selfName: String,
gradleModule: IdeaModule, gradleModule: IdeaModule,
@@ -1131,7 +863,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
private fun getKotlinModuleId(gradleModule: IdeaModule, kotlinModule: KotlinModule, resolverCtx: ProjectResolverContext) = private fun getKotlinModuleId(gradleModule: IdeaModule, kotlinModule: KotlinModule, resolverCtx: ProjectResolverContext) =
getGradleModuleQualifiedName(resolverCtx, gradleModule, kotlinModule.fullName()) getGradleModuleQualifiedName(resolverCtx, gradleModule, kotlinModule.fullName())
private fun getGradleModuleQualifiedName( internal fun getGradleModuleQualifiedName(
resolverCtx: ProjectResolverContext, resolverCtx: ProjectResolverContext,
gradleModule: IdeaModule, gradleModule: IdeaModule,
simpleName: String simpleName: String
@@ -1144,10 +876,13 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp
ignoreCase = true ignoreCase = true
) ?: false ) ?: false
private fun delegateToAndroidPlugin(kotlinTarget: KotlinTarget): Boolean = internal fun delegateToAndroidPlugin(compilation: KotlinCompilation): Boolean =
androidPluginPresent && compilation.platform == KotlinPlatform.ANDROID
internal fun delegateToAndroidPlugin(kotlinTarget: KotlinTarget): Boolean =
androidPluginPresent && kotlinTarget.platform == KotlinPlatform.ANDROID androidPluginPresent && kotlinTarget.platform == KotlinPlatform.ANDROID
private fun delegateToAndroidPlugin(kotlinSourceSet: KotlinSourceSet): Boolean = internal fun delegateToAndroidPlugin(kotlinSourceSet: KotlinSourceSet): Boolean =
androidPluginPresent && kotlinSourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID androidPluginPresent && kotlinSourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID
} }
} }
@@ -9,9 +9,11 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import org.gradle.tooling.model.idea.IdeaModule import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinDependency
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.idea.configuration.buildClasspathData import org.jetbrains.kotlin.idea.configuration.buildClasspathData
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.buildIDELibraryName import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.buildIDELibraryName
import org.jetbrains.kotlin.idea.configuration.mpp.KotlinDependenciesPreprocessor
import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.plugins.gradle.ExternalDependencyId import org.jetbrains.plugins.gradle.ExternalDependencyId
@@ -21,10 +23,15 @@ import java.io.File
// KT-29613, KT-29783 // KT-29613, KT-29783
internal class KotlinNativeLibrariesDependencySubstitutor( internal class KotlinNativeLibrariesDependencySubstitutor(
val mppModel: KotlinMPPGradleModel, private val mppModel: KotlinMPPGradleModel,
private val gradleModule: IdeaModule, private val gradleModule: IdeaModule,
private val resolverCtx: ProjectResolverContext private val resolverCtx: ProjectResolverContext
) { ) : KotlinDependenciesPreprocessor {
override fun invoke(dependencies: Iterable<KotlinDependency>): List<KotlinDependency> {
return substituteDependencies(dependencies.toList())
}
// Substitutes `ExternalDependency` entries that represent KLIBs with new dependency entries with proper type and name: // Substitutes `ExternalDependency` entries that represent KLIBs with new dependency entries with proper type and name:
// - every `FileCollectionDependency` is checked whether it points to an existing KLIB, and substituted if it is // - every `FileCollectionDependency` is checked whether it points to an existing KLIB, and substituted if it is
// - similarly for every `ExternalLibraryDependency` with `groupId == "Kotlin/Native"` (legacy KLIB provided by Gradle plugin <= 1.3.20) // - similarly for every `ExternalLibraryDependency` with `groupId == "Kotlin/Native"` (legacy KLIB provided by Gradle plugin <= 1.3.20)
@@ -0,0 +1,21 @@
/*
* 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.idea.configuration.mpp
import org.jetbrains.kotlin.gradle.KotlinDependency
internal object DistinctIdKotlinDependenciesPreprocessor : KotlinDependenciesPreprocessor {
override fun invoke(dependencies: Iterable<KotlinDependency>): List<KotlinDependency> {
return dependencies
.groupBy { dependency -> dependency.id }
.mapValues { (_, dependenciesWithSameId) ->
dependenciesWithSameId.firstOrNull { it.scope == "COMPILE" } ?: dependenciesWithSameId.lastOrNull()
}
.values
.filterNotNull()
}
}
@@ -0,0 +1,27 @@
/*
* 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.idea.configuration.mpp
import org.jetbrains.kotlin.gradle.KotlinDependency
internal interface KotlinDependenciesPreprocessor {
operator fun invoke(dependencies: Iterable<KotlinDependency>): List<KotlinDependency>
}
internal operator fun KotlinDependenciesPreprocessor.plus(
other: KotlinDependenciesPreprocessor
): KotlinDependenciesPreprocessor {
return SequentialKotlinDependenciesPreprocessor(this, other)
}
private class SequentialKotlinDependenciesPreprocessor(
private val first: KotlinDependenciesPreprocessor,
private val second: KotlinDependenciesPreprocessor
) : KotlinDependenciesPreprocessor {
override fun invoke(dependencies: Iterable<KotlinDependency>): List<KotlinDependency> {
return second(first(dependencies))
}
}
@@ -0,0 +1,86 @@
/*
* 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.idea.configuration.mpp
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ProjectData
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver.Companion.CompilationWithDependencies
import org.jetbrains.kotlin.idea.configuration.getMppModel
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesDependencySubstitutor
import org.jetbrains.plugins.gradle.model.ExternalSourceSet
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import com.intellij.openapi.util.Pair as IntelliJPair
internal typealias ModuleId = String
internal typealias ArtifactPath = String
internal data class PopulateModuleDependenciesContext(
val resolverCtx: ProjectResolverContext,
val mppModel: KotlinMPPGradleModel,
val gradleModule: IdeaModule,
val ideProject: DataNode<ProjectData>,
val ideModule: DataNode<ModuleData>,
val dependenciesPreprocessor: KotlinDependenciesPreprocessor,
val sourceSetMap: Map<ModuleId, IntelliJPair<DataNode<GradleSourceSetData>, ExternalSourceSet>>,
val artifactsMap: Map<ArtifactPath, ModuleId>,
val processedModuleIds: MutableSet<ModuleId> = mutableSetOf()
)
internal fun KotlinMPPGradleProjectResolver.Companion.createPopulateModuleDependenciesContext(
gradleModule: IdeaModule,
ideProject: DataNode<ProjectData>,
ideModule: DataNode<ModuleData>,
resolverCtx: ProjectResolverContext
): PopulateModuleDependenciesContext? {
val mppModel = resolverCtx.getMppModel(gradleModule) ?: return null
mppModel.dependencyMap.values.modifyDependenciesOnMppModules(ideProject, resolverCtx)
val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return null
val artifactsMap = ideProject.getUserData(GradleProjectResolver.CONFIGURATION_ARTIFACTS) ?: return null
val dependenciesPreprocessor = KotlinNativeLibrariesDependencySubstitutor(mppModel, gradleModule, resolverCtx)
.plus(DistinctIdKotlinDependenciesPreprocessor)
return PopulateModuleDependenciesContext(
resolverCtx = resolverCtx,
mppModel = mppModel,
gradleModule = gradleModule,
ideProject = ideProject,
ideModule = ideModule,
dependenciesPreprocessor = dependenciesPreprocessor,
sourceSetMap = sourceSetMap,
artifactsMap = artifactsMap,
)
}
internal fun PopulateModuleDependenciesContext.getDependencies(module: KotlinModule): List<KotlinDependency> {
return dependenciesPreprocessor(module.dependencies.mapNotNull { id -> mppModel.dependencyMap[id] })
}
internal fun PopulateModuleDependenciesContext.getCompilationsWithDependencies(
sourceSet: KotlinSourceSet
): List<CompilationWithDependencies> {
return mppModel.getCompilations(sourceSet).map { compilation -> CompilationWithDependencies(compilation, getDependencies(compilation)) }
}
internal fun KotlinCompilation.dependsOnSourceSet(mppModel: KotlinMPPGradleModel, sourceSet: KotlinSourceSet): Boolean {
return declaredSourceSets.any { containedSourceSet -> sourceSet.isOrDependsOnSourceSet(mppModel, containedSourceSet) }
}
internal fun KotlinSourceSet.isOrDependsOnSourceSet(mppModel: KotlinMPPGradleModel, sourceSet: KotlinSourceSet): Boolean {
if (this == sourceSet) return true
return this.declaredDependsOnSourceSets
.map { dependencySourceSetName -> mppModel.sourceSetsByName.getValue(dependencySourceSetName) }
.any { dependencySourceSet -> dependencySourceSet.isOrDependsOnSourceSet(mppModel, sourceSet) }
}
@@ -0,0 +1,39 @@
/*
* 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.idea.configuration.mpp
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ModuleDependencyData
import com.intellij.openapi.roots.DependencyScope
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
internal fun KotlinMPPGradleProjectResolver.Companion.addDependency(
fromModule: DataNode<*>, toModule: DataNode<*>, dependOnTestModule: Boolean
) {
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 nodeToModify = existing.singleOrNull() ?: existing.firstOrNull { it.scope == DependencyScope.COMPILE } ?: existing.firstOrNull()
if (nodeToModify != null) {
nodeToModify.scope = DependencyScope.COMPILE
nodeToModify.isProductionOnTestDependency = nodeToModify.isProductionOnTestDependency || dependOnTestModule
return
}
val moduleDependencyData = ModuleDependencyData(fromData, toData).also {
it.scope = DependencyScope.COMPILE
it.isExported = false
it.isProductionOnTestDependency = dependOnTestModule
}
fromModule.createChild(ProjectKeys.MODULE_DEPENDENCY, moduleDependencyData)
}
@@ -0,0 +1,32 @@
/*
* 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.idea.configuration.mpp
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.util.Pair
import org.jetbrains.kotlin.gradle.KotlinDependency
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesFixer
import org.jetbrains.plugins.gradle.model.ExternalSourceSet
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
@Throws(IllegalStateException::class)
internal fun KotlinMPPGradleProjectResolver.Companion.buildDependencies(
resolverCtx: ProjectResolverContext,
sourceSetMap: Map<String, Pair<DataNode<GradleSourceSetData>, ExternalSourceSet>>,
artifactsMap: Map<ArtifactPath, ModuleId>,
ownerDataNode: DataNode<GradleSourceSetData>,
dependencies: Collection<KotlinDependency>,
ideProject: DataNode<ProjectData>
) {
GradleProjectResolverUtil.buildDependencies(
resolverCtx, sourceSetMap, artifactsMap, ownerDataNode, dependencies, ideProject
)
KotlinNativeLibrariesFixer.applyTo(ownerDataNode, ideProject)
}
@@ -0,0 +1,45 @@
/*
* 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.idea.configuration.mpp
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinCompilation
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.kotlin.idea.configuration.kotlinSourceSet
import org.jetbrains.kotlin.idea.configuration.utils.getKotlinModuleId
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
internal fun KotlinMPPGradleProjectResolver.Companion.getCompilations(
gradleModule: IdeaModule,
mppModel: KotlinMPPGradleModel,
ideModule: DataNode<ModuleData>,
resolverCtx: ProjectResolverContext
): Sequence<Pair<DataNode<GradleSourceSetData>, KotlinCompilation>> {
val sourceSetsMap = HashMap<String, DataNode<GradleSourceSetData>>()
for (dataNode in ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)) {
if (dataNode.kotlinSourceSet != null) {
sourceSetsMap[dataNode.data.id] = dataNode
}
}
return sequence {
for (target in mppModel.targets) {
for (compilation in target.compilations) {
val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx)
val moduleDataNode = sourceSetsMap[moduleId] ?: continue
yield(moduleDataNode to compilation)
}
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.idea.configuration.mpp
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.kotlin.idea.configuration.utils.fullName
import org.jetbrains.kotlin.idea.configuration.utils.getKotlinModuleId
internal fun KotlinMPPGradleProjectResolver.Companion.populateModuleDependenciesByCompilations(
context: PopulateModuleDependenciesContext
): Unit = with(context) {
getCompilations(gradleModule, mppModel, ideModule, resolverCtx)
.filterNot { (_, compilation) -> delegateToAndroidPlugin(compilation) }
.filter { (_, compilation) -> processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx)) }
.forEach { (dataNode, compilation) ->
buildDependencies(
resolverCtx, sourceSetMap, artifactsMap, dataNode, getDependencies(compilation), ideProject
)
for (sourceSet in compilation.declaredSourceSets) {
if (sourceSet.fullName() == compilation.fullName()) continue
val targetDataNode = getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx) ?: continue
addDependency(dataNode, targetDataNode, sourceSet.isTestModule)
}
}
}
@@ -0,0 +1,234 @@
/*
* 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.idea.configuration.mpp
import com.google.common.graph.Graph
import com.intellij.openapi.externalSystem.model.DataNode
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver.Companion.CompilationWithDependencies
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.configuration.kotlinAndroidSourceSets
import org.jetbrains.kotlin.idea.configuration.kotlinSourceSet
import org.jetbrains.kotlin.idea.configuration.utils.createSourceSetVisibilityGraph
import org.jetbrains.kotlin.idea.configuration.utils.fullName
import org.jetbrains.kotlin.idea.configuration.utils.getKotlinModuleId
import org.jetbrains.kotlin.idea.configuration.utils.transitiveClosure
import org.jetbrains.plugins.gradle.model.ExternalDependency
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
internal fun KotlinMPPGradleProjectResolver.Companion.populateModuleDependenciesBySourceSetVisibilityGraph(
context: PopulateModuleDependenciesContext
): Unit = with(context) {
val sourceSetVisibilityGraph = createSourceSetVisibilityGraph(mppModel).transitiveClosure
for (sourceSet in sourceSetVisibilityGraph.nodes()) {
populateSourceSetInfos(context, sourceSetVisibilityGraph, sourceSet)
if (delegateToAndroidPlugin(sourceSet)) continue
val visibleSourceSets = sourceSetVisibilityGraph.successors(sourceSet)
val fromDataNode = getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx)?.cast<GradleSourceSetData>()
?: return
/* Add dependencies from current sourceSet to all visible source sets (dependsOn, test to production, ...)*/
for (visibleSourceSet in visibleSourceSets) {
val toDataNode = getSiblingKotlinModuleData(visibleSourceSet, gradleModule, ideModule, resolverCtx) ?: continue
addDependency(fromDataNode, toDataNode, visibleSourceSet.isTestModule)
}
if (!processedModuleIds.add(getKotlinModuleId(gradleModule, sourceSet, resolverCtx))) continue
val settings = dependencyPopulationSettings(mppModel, sourceSet)
val directDependencies = getDependencies(sourceSet).toSet()
val dependenciesFromVisibleSourceSets = getDependenciesFromVisibleSourceSets(settings, visibleSourceSets)
val dependenciesFromNativePropagation = getPropagatedNativeDependencies(settings, sourceSet)
val dependenciesFromPlatformPropagation = getPropagatedPlatformDependencies(sourceSet)
val dependencies = dependenciesPreprocessor(
directDependencies + dependenciesFromVisibleSourceSets +
dependenciesFromNativePropagation + dependenciesFromPlatformPropagation
)
buildDependencies(resolverCtx, sourceSetMap, artifactsMap, fromDataNode, dependencies, ideProject)
}
}
private data class DependencyPopulationSettings(
val forceNativeDependencyPropagation: Boolean,
val excludeInheritedNativeDependencies: Boolean
)
private fun dependencyPopulationSettings(mppModel: KotlinMPPGradleModel, sourceSet: KotlinSourceSet): DependencyPopulationSettings {
val forceNativeDependencyPropagation: Boolean
val excludeInheritedNativeDependencies: Boolean
if (mppModel.extraFeatures.isHMPPEnabled && sourceSet.actualPlatforms.singleOrNull() == KotlinPlatform.NATIVE) {
forceNativeDependencyPropagation = mppModel.extraFeatures.isNativeDependencyPropagationEnabled
excludeInheritedNativeDependencies = !forceNativeDependencyPropagation
} else {
forceNativeDependencyPropagation = false
excludeInheritedNativeDependencies = false
}
return DependencyPopulationSettings(
forceNativeDependencyPropagation = forceNativeDependencyPropagation,
excludeInheritedNativeDependencies = excludeInheritedNativeDependencies
)
}
private fun PopulateModuleDependenciesContext.getDependenciesFromVisibleSourceSets(
settings: DependencyPopulationSettings,
visibleSourceSets: Set<KotlinSourceSet>
): Set<KotlinDependency> {
val inheritedDependencies = visibleSourceSets
.flatMap { visibleSourceSet -> getDependencies(visibleSourceSet) }
return if (settings.excludeInheritedNativeDependencies) {
inheritedDependencies.filter { !it.name.startsWith(KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) }
} else {
inheritedDependencies
}.toSet()
}
private fun PopulateModuleDependenciesContext.getPropagatedNativeDependencies(
settings: DependencyPopulationSettings,
sourceSet: KotlinSourceSet
): Set<KotlinDependency> {
if (!settings.forceNativeDependencyPropagation) {
return emptySet()
}
return getPropagatedNativeDependencies(getCompilationsWithDependencies(sourceSet)).toSet()
}
/**
* We can't really commonize native platform libraries yet.
* But APIs for different targets may be very similar.
* E.g. ios_arm64 and ios_x64 have almost identical platform libraries
* We handle these special cases and resolve common sources for such
* targets against libraries of one of them. E.g. common sources for
* ios_x64 and ios_arm64 will be resolved against ios_arm64 libraries.
* Currently such special casing is available for Apple platforms
* (iOS, watchOS and tvOS) and native Android (ARM, X86).
* TODO: Do we need to support user's interop libraries too?
*/
private fun getPropagatedNativeDependencies(compilations: List<CompilationWithDependencies>): List<ExternalDependency> {
if (compilations.size <= 1) {
return emptyList()
}
val copyFrom = when {
compilations.all { it.isAppleCompilation } ->
compilations.selectFirstAvailableTarget(
"watchos_arm64", "watchos_arm32", "watchos_x86",
"ios_arm64", "ios_arm32", "ios_x64",
"tvos_arm64", "tvos_x64"
)
compilations.all { it.konanTarget?.startsWith("android") == true } ->
compilations.selectFirstAvailableTarget(
"android_arm64", "android_arm32", "android_x64", "android_x86"
)
else -> return emptyList()
}
return copyFrom.dependencyNames.mapNotNull { (name, dependency) ->
when {
!name.startsWith(KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) -> null // Support only default platform libs for now.
compilations.all { it.dependencyNames.containsKey(name) } -> dependency
else -> null
}
}
}
private val CompilationWithDependencies.isAppleCompilation: Boolean
get() = konanTarget?.let {
it.startsWith("ios") || it.startsWith("watchos") || it.startsWith("tvos")
} ?: false
private fun Iterable<CompilationWithDependencies>.selectFirstAvailableTarget(
@NonNls vararg targetsByPriority: String
): CompilationWithDependencies {
for (target in targetsByPriority) {
val result = firstOrNull { it.konanTarget == target }
if (result != null) {
return result
}
}
return first()
}
private fun PopulateModuleDependenciesContext.getPropagatedPlatformDependencies(
sourceSet: KotlinSourceSet
): Set<KotlinDependency> {
if (!mppModel.extraFeatures.isHMPPEnabled) {
return emptySet()
}
return getPropagatedPlatformDependencies(mppModel, sourceSet)
}
/**
* Source sets sharing code between JVM and Android are the only intermediate source sets that
* can effectively consume a dependency's platform artifact.
* When a library only offers a JVM variant, then Android and JVM consume this variant of the library.
*
* This will be replaced later on by [KT-43450](https://youtrack.jetbrains.com/issue/KT-43450)
*
* @return all dependencies being present across given JVM and Android compilations that this [sourceSet] can also participates in.
*/
private fun getPropagatedPlatformDependencies(
mppModel: KotlinMPPGradleModel,
sourceSet: KotlinSourceSet,
): Set<ExternalDependency> {
if (
sourceSet.actualPlatforms.platforms.sorted() == listOf(KotlinPlatform.JVM, KotlinPlatform.ANDROID).sorted()
) {
return mppModel.targets
.filter { target -> target.platform == KotlinPlatform.JVM || target.platform == KotlinPlatform.ANDROID }
.flatMap { target -> target.compilations }
.filter { compilation -> compilation.dependsOnSourceSet(mppModel, sourceSet) }
.map { targetCompilations -> targetCompilations.dependencies.mapNotNull(mppModel.dependencyMap::get).toSet() }
.reduceOrNull { acc, dependencies -> acc.intersect(dependencies) }.orEmpty()
}
return emptySet()
}
// TODO: Move this maybe to another semantic part of KotlinMPPGradleProjectResolver?
private fun KotlinMPPGradleProjectResolver.Companion.populateSourceSetInfos(
context: PopulateModuleDependenciesContext,
closedSourceSetGraph: Graph<KotlinSourceSet>,
sourceSet: KotlinSourceSet
) = with(context) {
val isAndroid = delegateToAndroidPlugin(sourceSet)
val fromDataNode = if (isAndroid) ideModule
else getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx) ?: return
val dependeeSourceSets = closedSourceSetGraph.successors(sourceSet)
val sourceSetInfos = if (isAndroid) {
ideModule.kotlinAndroidSourceSets?.filter {
(it.kotlinModule as? KotlinCompilation)?.declaredSourceSets?.contains(sourceSet) ?: false
} ?: emptyList()
} else {
listOfNotNull(fromDataNode.kotlinSourceSet)
}
for (sourceSetInfo in sourceSetInfos) {
if (sourceSetInfo.kotlinModule is KotlinCompilation) {
val selfName = sourceSetInfo.kotlinModule.fullName()
sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx)
}
}
}
inline fun <reified T : Any> DataNode<*>.cast(): DataNode<T> {
if (data !is T) {
throw ClassCastException("DataNode<${data.javaClass.canonicalName}> cannot be cast to DataNode<${T::class.java.canonicalName}>")
}
@Suppress("UNCHECKED_CAST")
return this as DataNode<T>
}
@@ -0,0 +1,22 @@
/*
* 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.idea.configuration.utils
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinCompilation
import org.jetbrains.kotlin.gradle.KotlinModule
import org.jetbrains.kotlin.gradle.compilationFullName
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
internal fun KotlinModule.fullName(simpleName: String = name) = when (this) {
is KotlinCompilation -> compilationFullName(simpleName, disambiguationClassifier)
else -> simpleName
}
internal fun KotlinMPPGradleProjectResolver.Companion.getKotlinModuleId(
gradleModule: IdeaModule, kotlinModule: KotlinModule, resolverCtx: ProjectResolverContext
) = getGradleModuleQualifiedName(resolverCtx, gradleModule, kotlinModule.fullName())
@@ -0,0 +1,89 @@
/*
* 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.
*/
@file:Suppress("UnstableApiUsage")
package org.jetbrains.kotlin.idea.configuration.utils
import com.google.common.graph.*
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.gradle.KotlinPlatform
import org.jetbrains.kotlin.gradle.KotlinSourceSet
internal fun createSourceSetVisibilityGraph(model: KotlinMPPGradleModel): ImmutableGraph<KotlinSourceSet> {
val graph = createSourceSetDependsOnGraph(model)
graph.putInferredTestToProductionEdges()
return graph.immutable
}
internal fun createSourceSetDependsOnGraph(model: KotlinMPPGradleModel): MutableGraph<KotlinSourceSet> {
return createSourceSetDependsOnGraph(model.sourceSetsByName)
}
internal fun createSourceSetDependsOnGraph(
sourceSetsByName: Map<String, KotlinSourceSet>
): MutableGraph<KotlinSourceSet> {
val graph = GraphBuilder.directed().build<KotlinSourceSet>()
val sourceSets = sourceSetsByName.values.toSet()
for (sourceSet in sourceSets) {
graph.addNode(sourceSet)
val dependsOnSourceSets = getFixedDependsOnSourceSets(sourceSetsByName, sourceSet)
for (dependsOnSourceSet in dependsOnSourceSets) {
graph.addNode(dependsOnSourceSet)
graph.putEdge(sourceSet, dependsOnSourceSet)
}
}
return graph
}
internal fun MutableGraph<KotlinSourceSet>.putInferredTestToProductionEdges() {
val sourceSets = this.nodes()
for (sourceSet in sourceSets) {
if (sourceSet.isTestModule) {
@OptIn(UnsafeTestSourceSetHeuristicApi::class)
val predictedMainSourceSetName = predictedProductionSourceSetName(sourceSet.name)
val predictedMainSourceSet = sourceSets.firstOrNull { it.name == predictedMainSourceSetName } ?: continue
putEdge(sourceSet, predictedMainSourceSet)
}
}
}
private fun getFixedDependsOnSourceSets(
sourceSetsByName: Map<String, KotlinSourceSet>, sourceSet: KotlinSourceSet
): Set<KotlinSourceSet> {
/*
Workaround for older Kotlin Gradle Plugin versions that did not explicitly declare a dependsOn relation
from a Kotlin source set to "commonMain"
(Can probably be dropped in Kotlin 1.5)
*/
val implicitDependsOnEdgeForAndroid = if (
sourceSet.actualPlatforms.contains(KotlinPlatform.ANDROID) && sourceSet.declaredDependsOnSourceSets.isEmpty()
) {
val commonSourceSetName = if (sourceSet.isTestModule) KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME
else KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME
listOfNotNull(sourceSetsByName[commonSourceSetName])
} else emptyList()
return sourceSet.declaredDependsOnSourceSets.map(sourceSetsByName::getValue)
.plus(implicitDependsOnEdgeForAndroid)
/*
Gracefully filter out source sets that declare a dependency on themselves.
This also fixes KTIJ-1025
*/
.filter { dependencySourceSet -> dependencySourceSet != sourceSet }
.toSet()
}
/**
* @see Graphs.transitiveClosure
*/
internal val <T> Graph<T>.transitiveClosure: Graph<T> get() = Graphs.transitiveClosure(this)
/**
* @see ImmutableGraph.copyOf
*/
internal val <T> Graph<T>.immutable: ImmutableGraph<T> get() = ImmutableGraph.copyOf(this)
@@ -0,0 +1,42 @@
/*
* 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.idea.configuration.utils
// TODO: Replace heuristic with proper import!
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
annotation class UnsafeTestSourceSetHeuristicApi
@UnsafeTestSourceSetHeuristicApi
internal fun isProbablyTestSourceSet(name: String): Boolean {
return name.endsWith("Test") || name == "test"
}
@UnsafeTestSourceSetHeuristicApi
internal fun isProbablyProductionSourceSet(name: String): Boolean {
return !isProbablyTestSourceSet(name)
}
@UnsafeTestSourceSetHeuristicApi
internal fun predictedProductionSourceSetName(testSourceSetName: String): String {
/*
This implementation uses a lot of implicit knowledge and should be replaced.
This implicit knowledge is/was used at other places as well that should also
use an explicit API once available.
See:
https://github.com/JetBrains/intellij-community/blob/c05e8d6de8d1c690eb99c2fff8629c0de373c48f/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/CommonGradleProjectResolverExtension.java#L153
https://github.com/JetBrains/intellij-kotlin/blob/fe6ee9dfb42ad1cdd9e55449e2481125f1631133/gradle/gradle-idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt#L424
https://github.com/JetBrains/intellij-kotlin/blob/fe6ee9dfb42ad1cdd9e55449e2481125f1631133/gradle/gradle-idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt#L627
*/
if (testSourceSetName.endsWith("Test")) {
return testSourceSetName.removeSuffix("Test") + "Main"
}
if (testSourceSetName == "test") {
return "main"
}
return testSourceSetName + "Main"
}
@@ -0,0 +1,167 @@
/*
* 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.idea.configuration.utils
import com.google.common.graph.EndpointPair
import com.google.common.graph.EndpointPair.ordered
import com.google.common.graph.Graph
import com.google.common.graph.GraphBuilder
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.gradle.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.junit.Test
import kotlin.test.assertEquals
class KotlinSourceSetGraphUtilTest {
@Test
fun `simple layout`() {
val sourceSetsByName = mapOf(
createKotlinSourceSetPair(COMMON_MAIN_SOURCE_SET_NAME),
createKotlinSourceSetPair(COMMON_TEST_SOURCE_SET_NAME),
createKotlinSourceSetPair("jvmMain", setOf(COMMON_MAIN_SOURCE_SET_NAME)),
createKotlinSourceSetPair("jvmTest", setOf(COMMON_TEST_SOURCE_SET_NAME))
)
val dependsOnGraph = createSourceSetDependsOnGraph(sourceSetsByName)
assertEquals(
listOf(
ordered("jvmMain", COMMON_MAIN_SOURCE_SET_NAME),
ordered("jvmTest", COMMON_TEST_SOURCE_SET_NAME)
).sorted(),
dependsOnGraph.nameEdges().sorted()
)
}
@Test
fun `hierarchical layout`() {
val sourceSetsByName = mapOf(
createKotlinSourceSetPair(COMMON_MAIN_SOURCE_SET_NAME),
createKotlinSourceSetPair(COMMON_TEST_SOURCE_SET_NAME),
createKotlinSourceSetPair("jvmAndJsMain", setOf(COMMON_MAIN_SOURCE_SET_NAME)),
createKotlinSourceSetPair("jvmAndJsTest", setOf(COMMON_TEST_SOURCE_SET_NAME)),
createKotlinSourceSetPair("jvmMain", setOf("jvmAndJsMain")),
createKotlinSourceSetPair("jvmTest", setOf("jvmAndJsTest")),
createKotlinSourceSetPair("jsMain", setOf("jvmAndJsMain")),
createKotlinSourceSetPair("jsTest", setOf("jvmAndJsTest")),
)
val dependsOnGraph = createSourceSetDependsOnGraph(sourceSetsByName)
assertEquals(
listOf(
ordered("jvmMain", "jvmAndJsMain"),
ordered("jvmTest", "jvmAndJsTest"),
ordered("jsMain", "jvmAndJsMain"),
ordered("jsTest", "jvmAndJsTest"),
ordered("jvmAndJsMain", COMMON_MAIN_SOURCE_SET_NAME),
ordered("jvmAndJsTest", COMMON_TEST_SOURCE_SET_NAME)
).sorted(),
dependsOnGraph.nameEdges().sorted()
)
}
@Test
fun `source set depending on multiple other source sets`() {
val sourceSetsByName = mapOf(
createKotlinSourceSetPair("common1"),
createKotlinSourceSetPair("common2"),
createKotlinSourceSetPair("consumerA", setOf("common1", "common2")),
createKotlinSourceSetPair("consumerB", setOf("common1", "consumerA"))
)
val dependsOnGraph = createSourceSetDependsOnGraph(sourceSetsByName)
assertEquals(
listOf(
ordered("consumerA", "common1"),
ordered("consumerA", "common2"),
ordered("consumerB", "common1"),
ordered("consumerB", "consumerA")
).sorted(),
dependsOnGraph.nameEdges().sorted()
)
}
@Test
fun `infer default test to production edges`() {
val sourceSetsByName = mapOf(
createKotlinSourceSetPair(COMMON_MAIN_SOURCE_SET_NAME),
createKotlinSourceSetPair(COMMON_TEST_SOURCE_SET_NAME, isTestModule = true),
createKotlinSourceSetPair("main"),
createKotlinSourceSetPair("test", isTestModule = true),
createKotlinSourceSetPair("abcMain"),
createKotlinSourceSetPair("abcTest", isTestModule = true)
)
val graph = GraphBuilder.directed().build<KotlinSourceSet>()
sourceSetsByName.values.forEach { sourceSet -> graph.addNode(sourceSet) }
graph.putInferredTestToProductionEdges()
assertEquals(
listOf(
ordered(COMMON_TEST_SOURCE_SET_NAME, COMMON_MAIN_SOURCE_SET_NAME),
ordered("test", "main"),
ordered("abcTest", "abcMain")
).sorted(),
graph.nameEdges().sorted()
)
}
@Test
fun `depends on graph will automatically infer missing dependsOn edges for Android source sets`() {
val sourceSetsByName = mapOf(
createKotlinSourceSetPair(COMMON_MAIN_SOURCE_SET_NAME),
createKotlinSourceSetPair(COMMON_TEST_SOURCE_SET_NAME, isTestModule = true),
createKotlinSourceSetPair("androidMain", platforms = setOf(KotlinPlatform.ANDROID)),
createKotlinSourceSetPair("androidTest", platforms = setOf(KotlinPlatform.ANDROID), isTestModule = true)
)
assertEquals(
listOf(
ordered("androidMain", COMMON_MAIN_SOURCE_SET_NAME),
ordered("androidTest", COMMON_TEST_SOURCE_SET_NAME)
).sorted(),
createSourceSetDependsOnGraph(sourceSetsByName).nameEdges().sorted()
)
}
}
private fun createKotlinSourceSetPair(
name: String,
declaredDependsOnSourceSets: Set<String> = emptySet(),
allDependsOnSourceSets: Set<String> = declaredDependsOnSourceSets,
platforms: Set<KotlinPlatform> = emptySet(),
isTestModule: Boolean = false,
): Pair<String, KotlinSourceSet> = name to KotlinSourceSetImpl(
name = name,
languageSettings = KotlinLanguageSettingsImpl(
languageVersion = null,
apiVersion = null,
isProgressiveMode = false,
enabledLanguageFeatures = emptySet(),
experimentalAnnotationsInUse = emptySet(),
compilerPluginArguments = emptyArray(),
compilerPluginClasspath = emptySet(),
freeCompilerArgs = emptyArray()
),
sourceDirs = emptySet(),
resourceDirs = emptySet(),
dependencies = emptyArray(),
declaredDependsOnSourceSets = declaredDependsOnSourceSets,
allDependsOnSourceSets = allDependsOnSourceSets,
defaultActualPlatforms = KotlinPlatformContainerImpl().apply { pushPlatforms(platforms) },
defaultIsTestModule = isTestModule
)
private fun Graph<KotlinSourceSet>.nameEdges() = edges()
.map { edge -> ordered(edge.source().name, edge.target().name) }
private fun Iterable<EndpointPair<String>>.sorted() = this.sortedBy { it.source() + " -> " + it.target() }
@@ -0,0 +1,21 @@
/*
* 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.idea.configuration.utils
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(UnsafeTestSourceSetHeuristicApi::class)
class TestSourceSetUtilsTest {
@Test
fun `test predicted production source set name for various examples`() {
assertEquals("commonMain", predictedProductionSourceSetName("commonTest"))
assertEquals("main", predictedProductionSourceSetName("test"))
assertEquals("abcMain", predictedProductionSourceSetName("abc"))
assertEquals("abctestMain", predictedProductionSourceSetName("abctest"))
}
}
+7 -1
View File
@@ -12,11 +12,17 @@ dependencies {
compileOnly(intellijPluginDep("gradle")) compileOnly(intellijPluginDep("gradle"))
compileOnly(intellijDep()) { includeJars("slf4j-api-1.7.25") } compileOnly(intellijDep()) { includeJars("slf4j-api-1.7.25") }
testImplementation(commonDep("junit:junit"))
testImplementation(projectTests(":compiler:tests-common"))
testCompileOnly(projectTests(":idea:idea-test-framework"))
testCompileOnly(intellijDep())
} }
sourceSets { sourceSets {
"main" { projectDefault() } "main" { projectDefault() }
"test" {} "test" { projectDefault() }
} }
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> { tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
@@ -0,0 +1,22 @@
/*
* 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
@ExperimentalGradleToolingApi
fun KotlinMPPGradleModel.getCompilations(sourceSet: KotlinSourceSet): Set<KotlinCompilation> {
return targets.flatMap { target -> target.compilations }
.filter { compilation -> compilationDependsOnSourceSet(compilation, sourceSet) }
.toSet()
}
@ExperimentalGradleToolingApi
fun KotlinMPPGradleModel.compilationDependsOnSourceSet(
compilation: KotlinCompilation, sourceSet: KotlinSourceSet
): Boolean {
return compilation.declaredSourceSets.any { sourceSetInCompilation ->
sourceSetInCompilation == sourceSet || sourceSetInCompilation.isDependsOn(this, sourceSet)
}
}
@@ -16,30 +16,25 @@ class KotlinSourceSetProto(
private val dependencies: () -> Array<KotlinDependencyId>, private val dependencies: () -> Array<KotlinDependencyId>,
val dependsOnSourceSets: Set<String> val dependsOnSourceSets: Set<String>
) { ) {
fun buildKotlinSourceSetImpl( fun buildKotlinSourceSetImpl(
doBuildDependencies: Boolean, doBuildDependencies: Boolean,
allSourceSetsProtosByNames: Map<String, KotlinSourceSetProto>, allSourceSetsProtosByNames: Map<String, KotlinSourceSetProto>,
dependsOnCache: HashMap<String, Set<String>>
) = KotlinSourceSetImpl( ) = KotlinSourceSetImpl(
name, name = name,
languageSettings, languageSettings = languageSettings,
sourceDirs, sourceDirs = sourceDirs,
resourceDirs, resourceDirs = resourceDirs,
if (doBuildDependencies) dependencies.invoke() else emptyArray(), dependencies = if (doBuildDependencies) dependencies.invoke() else emptyArray(),
calculateDependsOnClosure(this, allSourceSetsProtosByNames, dependsOnCache) declaredDependsOnSourceSets = dependsOnSourceSets,
allDependsOnSourceSets = allDependsOnSourceSets(allSourceSetsProtosByNames)
) )
}
private fun calculateDependsOnClosure( fun KotlinSourceSetProto.allDependsOnSourceSets(sourceSetsByName: Map<String, KotlinSourceSetProto>): Set<String> {
currentSourceSetProto: KotlinSourceSetProto, return mutableSetOf<String>().apply {
sourceSetsMap: Map<String, KotlinSourceSetProto>, addAll(dependsOnSourceSets)
cache: MutableMap<String, Set<String>> dependsOnSourceSets.map(sourceSetsByName::getValue).forEach { dependsOnSourceSet ->
): Set<String> { addAll(dependsOnSourceSet.allDependsOnSourceSets(sourceSetsByName))
return cache.computeIfAbsent(currentSourceSetProto.name) {
currentSourceSetProto.dependsOnSourceSets.flatMap { name ->
val nextSourceSet = sourceSetsMap[name] ?: error("Source set $name is not found in map $sourceSetsMap")
calculateDependsOnClosure(nextSourceSet, sourceSetsMap, cache).union(setOf(name))
}.toSet()
} }
} }
} }
@@ -50,29 +45,41 @@ class KotlinSourceSetImpl(
override val sourceDirs: Set<File>, override val sourceDirs: Set<File>,
override val resourceDirs: Set<File>, override val resourceDirs: Set<File>,
override val dependencies: Array<KotlinDependencyId>, override val dependencies: Array<KotlinDependencyId>,
override val dependsOnSourceSets: Set<String>, override val declaredDependsOnSourceSets: Set<String>,
val defaultPlatform: KotlinPlatformContainerImpl = KotlinPlatformContainerImpl(), @Suppress("OverridingDeprecatedMember")
override val allDependsOnSourceSets: Set<String>,
defaultActualPlatforms: KotlinPlatformContainerImpl = KotlinPlatformContainerImpl(),
defaultIsTestModule: Boolean = false
) : KotlinSourceSet { ) : KotlinSourceSet {
constructor(kotlinSourceSet: KotlinSourceSet, cloningCache: MutableMap<Any, Any>) : this( @Suppress("DEPRECATION")
kotlinSourceSet.name, constructor(kotlinSourceSet: KotlinSourceSet) : this(
KotlinLanguageSettingsImpl(kotlinSourceSet.languageSettings), name = kotlinSourceSet.name,
HashSet(kotlinSourceSet.sourceDirs), languageSettings = KotlinLanguageSettingsImpl(kotlinSourceSet.languageSettings),
HashSet(kotlinSourceSet.resourceDirs), sourceDirs = HashSet(kotlinSourceSet.sourceDirs),
kotlinSourceSet.dependencies, resourceDirs = HashSet(kotlinSourceSet.resourceDirs),
HashSet(kotlinSourceSet.dependsOnSourceSets), dependencies = kotlinSourceSet.dependencies.clone(),
KotlinPlatformContainerImpl(kotlinSourceSet.actualPlatforms) declaredDependsOnSourceSets = HashSet(kotlinSourceSet.declaredDependsOnSourceSets),
allDependsOnSourceSets = HashSet(kotlinSourceSet.allDependsOnSourceSets),
defaultActualPlatforms = KotlinPlatformContainerImpl(kotlinSourceSet.actualPlatforms)
) { ) {
this.isTestModule = kotlinSourceSet.isTestModule this.isTestModule = kotlinSourceSet.isTestModule
} }
override var actualPlatforms: KotlinPlatformContainer = defaultPlatform override var actualPlatforms: KotlinPlatformContainer = defaultActualPlatforms
internal set internal set
override var isTestModule: Boolean = false override var isTestModule: Boolean = defaultIsTestModule
internal set internal set
override fun toString() = name override fun toString() = name
init {
@Suppress("DEPRECATION")
require(allDependsOnSourceSets.containsAll(declaredDependsOnSourceSets)) {
"Inconsistent source set dependencies: 'allDependsOnSourceSets' is expected to contain all 'declaredDependsOnSourceSets'"
}
}
} }
data class KotlinLanguageSettingsImpl( data class KotlinLanguageSettingsImpl(
@@ -128,7 +135,7 @@ data class KotlinNativeCompilationExtensionsImpl(
data class KotlinCompilationImpl( data class KotlinCompilationImpl(
override val name: String, override val name: String,
override val allSourceSets: Collection<KotlinSourceSet>, override val allSourceSets: Collection<KotlinSourceSet>,
override val defaultSourceSets: Collection<KotlinSourceSet>, override val declaredSourceSets: Collection<KotlinSourceSet>,
override val dependencies: Array<KotlinDependencyId>, override val dependencies: Array<KotlinDependencyId>,
override val output: KotlinCompilationOutput, override val output: KotlinCompilationOutput,
override val arguments: KotlinCompilationArguments, override val arguments: KotlinCompilationArguments,
@@ -139,15 +146,15 @@ data class KotlinCompilationImpl(
// create deep copy // create deep copy
constructor(kotlinCompilation: KotlinCompilation, cloningCache: MutableMap<Any, Any>) : this( constructor(kotlinCompilation: KotlinCompilation, cloningCache: MutableMap<Any, Any>) : this(
kotlinCompilation.name, name = kotlinCompilation.name,
cloneSourceSetsWithCaching(kotlinCompilation.allSourceSets, cloningCache), declaredSourceSets = cloneSourceSetsWithCaching(kotlinCompilation.declaredSourceSets, cloningCache),
cloneSourceSetsWithCaching(kotlinCompilation.defaultSourceSets, cloningCache), allSourceSets = cloneSourceSetsWithCaching(kotlinCompilation.allSourceSets, cloningCache),
kotlinCompilation.dependencies, dependencies = kotlinCompilation.dependencies,
KotlinCompilationOutputImpl(kotlinCompilation.output), output = KotlinCompilationOutputImpl(kotlinCompilation.output),
KotlinCompilationArgumentsImpl(kotlinCompilation.arguments), arguments = KotlinCompilationArgumentsImpl(kotlinCompilation.arguments),
kotlinCompilation.dependencyClasspath, dependencyClasspath = kotlinCompilation.dependencyClasspath,
KotlinTaskPropertiesImpl(kotlinCompilation.kotlinTaskProperties), kotlinTaskProperties = KotlinTaskPropertiesImpl(kotlinCompilation.kotlinTaskProperties),
kotlinCompilation.nativeExtensions?.let(::KotlinNativeCompilationExtensionsImpl) nativeExtensions = kotlinCompilation.nativeExtensions?.let(::KotlinNativeCompilationExtensionsImpl)
) { ) {
disambiguationClassifier = kotlinCompilation.disambiguationClassifier disambiguationClassifier = kotlinCompilation.disambiguationClassifier
platform = kotlinCompilation.platform platform = kotlinCompilation.platform
@@ -158,7 +165,7 @@ data class KotlinCompilationImpl(
override lateinit var platform: KotlinPlatform override lateinit var platform: KotlinPlatform
internal set internal set
// TODO: Logic like this is duplicated *and different*
override val isTestModule: Boolean override val isTestModule: Boolean
get() = name == KotlinCompilation.TEST_COMPILATION_NAME get() = name == KotlinCompilation.TEST_COMPILATION_NAME
|| platform == KotlinPlatform.ANDROID && name.contains("Test") || platform == KotlinPlatform.ANDROID && name.contains("Test")
@@ -171,7 +178,7 @@ data class KotlinCompilationImpl(
cloningCache: MutableMap<Any, Any> cloningCache: MutableMap<Any, Any>
): List<KotlinSourceSet> = ): List<KotlinSourceSet> =
sourceSets.map { initialSourceSet -> sourceSets.map { initialSourceSet ->
(cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl(initialSourceSet, cloningCache).also { (cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl(initialSourceSet).also {
cloningCache[initialSourceSet] = it cloningCache[initialSourceSet] = it
} }
} }
@@ -250,7 +257,7 @@ data class ExtraFeaturesImpl(
) : ExtraFeatures ) : ExtraFeatures
data class KotlinMPPGradleModelImpl( data class KotlinMPPGradleModelImpl(
override val sourceSets: Map<String, KotlinSourceSet>, override val sourceSetsByName: Map<String, KotlinSourceSet>,
override val targets: Collection<KotlinTarget>, override val targets: Collection<KotlinTarget>,
override val extraFeatures: ExtraFeatures, override val extraFeatures: ExtraFeatures,
override val kotlinNativeHome: String, override val kotlinNativeHome: String,
@@ -259,11 +266,9 @@ data class KotlinMPPGradleModelImpl(
) : KotlinMPPGradleModel { ) : KotlinMPPGradleModel {
constructor(mppModel: KotlinMPPGradleModel, cloningCache: MutableMap<Any, Any>) : this( constructor(mppModel: KotlinMPPGradleModel, cloningCache: MutableMap<Any, Any>) : this(
mppModel.sourceSets.mapValues { initialSourceSet -> mppModel.sourceSetsByName.mapValues { initialSourceSet ->
(cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl( (cloningCache[initialSourceSet] as? KotlinSourceSet) ?: KotlinSourceSetImpl(initialSourceSet.value)
initialSourceSet.value, .also { cloningCache[initialSourceSet] = it }
cloningCache
).also { cloningCache[initialSourceSet] = it }
}, },
mppModel.targets.map { initialTarget -> mppModel.targets.map { initialTarget ->
(cloningCache[initialTarget] as? KotlinTarget) ?: KotlinTargetImpl(initialTarget, cloningCache).also { (cloningCache[initialTarget] as? KotlinTarget) ?: KotlinTargetImpl(initialTarget, cloningCache).also {
@@ -289,20 +294,20 @@ class KotlinPlatformContainerImpl() : KotlinPlatformContainer {
get() = myPlatforms != null get() = myPlatforms != null
constructor(platform: KotlinPlatformContainer) : this() { constructor(platform: KotlinPlatformContainer) : this() {
myPlatforms = HashSet<KotlinPlatform>(platform.platforms) myPlatforms = HashSet(platform.platforms)
} }
override val platforms: Collection<KotlinPlatform> override val platforms: Set<KotlinPlatform>
get() = myPlatforms ?: defaultCommonPlatform get() = myPlatforms ?: defaultCommonPlatform
override fun supports(simplePlatform: KotlinPlatform): Boolean = platforms.contains(simplePlatform) override fun supports(simplePlatform: KotlinPlatform): Boolean = platforms.contains(simplePlatform)
override fun addSimplePlatforms(platforms: Collection<KotlinPlatform>) { override fun pushPlatforms(platforms: Iterable<KotlinPlatform>) {
(myPlatforms ?: HashSet<KotlinPlatform>().apply { myPlatforms = this }).let { myPlatforms = (myPlatforms ?: LinkedHashSet()).apply {
it.addAll(platforms) addAll(platforms)
if (it.contains(KotlinPlatform.COMMON)) { if (contains(KotlinPlatform.COMMON)) {
it.clear() clear()
it.addAll(defaultCommonPlatform) addAll(defaultCommonPlatform)
} }
} }
} }
@@ -0,0 +1,307 @@
/*
* 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 org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@OptIn(ExperimentalGradleToolingApi::class)
class KotlinMppGradleModelExtensionsTest {
@Test
fun resolveDeclaredDependsOnSourceSets() {
val commonMain = createKotlinSourceSet("commonMain")
val appleMain = createKotlinSourceSet("appleMain", declaredDependsOnSourceSets = setOf("commonMain"))
val macosMain = createKotlinSourceSet("macosMain", declaredDependsOnSourceSets = setOf("appleMain"))
val iosMain = createKotlinSourceSet("iosMain", declaredDependsOnSourceSets = setOf("appleMain", "commonMain"))
val model = createKotlinMPPGradleModel(
sourceSets = setOf(commonMain, appleMain, macosMain, iosMain)
)
assertEquals(
emptySet(), model.resolveDeclaredDependsOnSourceSets(commonMain),
"Expected no declared dependency source sets for commonMain"
)
assertEquals(
setOf(commonMain), model.resolveDeclaredDependsOnSourceSets(appleMain),
"Expected only declared dependency for 'appleMain'"
)
assertEquals(
setOf(appleMain, commonMain), model.resolveDeclaredDependsOnSourceSets(iosMain),
"Expected only declared dependency for 'iosMain'"
)
}
@Test
fun `resolveDeclaredDependsOnSourceSets is graceful for missing source sets`() {
val commonMain = createKotlinSourceSet("commonMain")
val macosMain = createKotlinSourceSet("macosMain", declaredDependsOnSourceSets = setOf("commonMain", "missing"))
val model = createKotlinMPPGradleModel(sourceSets = setOf(commonMain, macosMain))
assertEquals(
setOf(commonMain), model.resolveDeclaredDependsOnSourceSets(macosMain),
"Expected declaredDependencySourceSets to ignore missing dependency source set"
)
}
@Test
fun resolveAllDependsOnSourceSets() {
val commonMain = createKotlinSourceSet("commonMain")
val appleMain = createKotlinSourceSet("appleMain", declaredDependsOnSourceSets = setOf("commonMain"))
val x64Main = createKotlinSourceSet("x64Main", declaredDependsOnSourceSets = setOf("commonMain"))
val macosMain = createKotlinSourceSet("macosMain", declaredDependsOnSourceSets = setOf("appleMain"))
val macosX64Main = createKotlinSourceSet("macosX64Main", declaredDependsOnSourceSets = setOf("appleMain", "x64Main"))
val macosArm64Main = createKotlinSourceSet("macosArm64Main", declaredDependsOnSourceSets = setOf("appleMain"))
val model = createKotlinMPPGradleModel(sourceSets = setOf(commonMain, appleMain, x64Main, macosMain, macosX64Main, macosArm64Main))
assertEquals(
setOf(appleMain, x64Main, commonMain),
model.resolveAllDependsOnSourceSets(macosX64Main),
)
assertEquals(
setOf(appleMain, x64Main, commonMain).sortedBy { it.name },
model.resolveAllDependsOnSourceSets(macosX64Main).sortedBy { it.name }.toList(),
)
assertEquals(
setOf(appleMain, commonMain),
model.resolveAllDependsOnSourceSets(macosArm64Main).toSet(),
)
assertEquals(
setOf(commonMain),
model.resolveAllDependsOnSourceSets(appleMain).toSet(),
"Expected only 'commonMain' for 'appleMain'"
)
assertEquals(
emptySet(), model.resolveAllDependsOnSourceSets(commonMain).toSet(),
"Expected empty set for 'commonMain'"
)
}
@Test
fun `resolving source sets with self references or loops`() {
val a = createKotlinSourceSet("a", declaredDependsOnSourceSets = setOf(""))
val b = createKotlinSourceSet("b", declaredDependsOnSourceSets = setOf("a", "c")) // loop d -> c -> b -> c
val c = createKotlinSourceSet("c", declaredDependsOnSourceSets = setOf("b", "c")) // self reference c -> c
val d = createKotlinSourceSet("d", declaredDependsOnSourceSets = setOf("c"))
val model = createKotlinMPPGradleModel(sourceSets = setOf(a, b, c, d))
assertEquals(
setOf(a, b, c).sortedBy { it.name },
model.resolveAllDependsOnSourceSets(d).sortedBy { it.name },
"Expected dependency loop to be handled gracefully"
)
assertEquals(
setOf(a, b, c).sortedBy { it.name },
model.resolveAllDependsOnSourceSets(c).sortedBy { it.name },
"Expected self dependency to be resolved for 'resolveAllDependsOnSourceSets'"
)
assertEquals(
setOf(b, c).sortedBy { it.name },
model.resolveDeclaredDependsOnSourceSets(c).sortedBy { it.name },
"Expected self dependency to be resolved by 'resolveDeclaredDependsOnSourceSets'"
)
}
@Test
fun isDependsOn() {
val commonMain = createKotlinSourceSet("commonMain")
val appleMain = createKotlinSourceSet("appleMain", declaredDependsOnSourceSets = setOf("commonMain"))
val x64Main = createKotlinSourceSet("x64Main", declaredDependsOnSourceSets = setOf("commonMain"))
val macosX64Main = createKotlinSourceSet("macosX64Main", declaredDependsOnSourceSets = setOf("appleMain", "x64Main"))
val macosArm64Main = createKotlinSourceSet("macosArm64Main", declaredDependsOnSourceSets = setOf("appleMain"))
val model = createKotlinMPPGradleModel(sourceSets = setOf(commonMain, appleMain, x64Main, macosX64Main))
assertTrue(
model.isDependsOn(from = appleMain, to = commonMain),
"Expected isDependsOn from appleMain to commonMain"
)
assertTrue(
model.isDependsOn(from = x64Main, to = commonMain),
"Expected isDependsOn from x64Main to commonMain"
)
assertTrue(
model.isDependsOn(from = macosX64Main, to = commonMain),
"Expected isDependsOn from macosX64Main to commonMain"
)
assertTrue(
model.isDependsOn(from = macosX64Main, to = appleMain),
"Expected isDependsOn from macosX64Main to appleMain"
)
assertTrue(
model.isDependsOn(from = macosX64Main, to = commonMain),
"Expected isDependsOn from macosX64Main to commonMain"
)
assertTrue(
model.isDependsOn(from = macosX64Main, to = appleMain),
"Expected isDependsOn from macosX64Main to appleMain"
)
assertTrue(
model.isDependsOn(from = macosX64Main, to = x64Main),
"Expected isDependsOn from macosX64Main to x64Main"
)
assertTrue(
model.isDependsOn(from = macosArm64Main, to = commonMain),
"Expected isDependsOn from macosArm64Main to commonMain"
)
assertTrue(
model.isDependsOn(from = macosArm64Main, to = appleMain),
"Expected isDependsOn from macosArm64Main to appleMain"
)
assertFalse(
model.isDependsOn(from = macosArm64Main, to = x64Main),
"Expected false isDependsOn from macosArm64 to x64Main"
)
for (sourceSet in model.sourceSetsByName.values) {
assertFalse(
model.isDependsOn(from = commonMain, to = sourceSet),
"Expected false isDependsOn from commonMain to ${sourceSet.name}"
)
assertFalse(
model.isDependsOn(from = sourceSet, to = sourceSet),
"Expected false isDependsOn for same source set"
)
assertFalse(
sourceSet.isDependsOn(model, sourceSet),
"Expected false isDependsOn for same source set"
)
}
}
@Test
fun compilationDependsOnSourceSet() {
val commonMain = createKotlinSourceSet("commonMain")
val appleMain = createKotlinSourceSet("appleMain", declaredDependsOnSourceSets = setOf("commonMain"))
val macosMain = createKotlinSourceSet("macosMain", declaredDependsOnSourceSets = setOf("appleMain"))
val iosMain = createKotlinSourceSet("iosMain", declaredDependsOnSourceSets = setOf("appleMain"))
val metadataCompilation = createKotlinCompilation(defaultSourceSets = setOf(commonMain))
val macosMainCompilation = createKotlinCompilation(defaultSourceSets = setOf(macosMain))
val iosMainCompilation = createKotlinCompilation(defaultSourceSets = setOf(iosMain))
val model = createKotlinMPPGradleModel(sourceSets = setOf(commonMain, appleMain, macosMain, iosMain))
assertTrue(
model.compilationDependsOnSourceSet(iosMainCompilation, iosMain),
"Expected iosMainCompilation depending on iosMain"
)
assertTrue(
model.compilationDependsOnSourceSet(iosMainCompilation, appleMain),
"Expected iosMainCompilation depending on appleMain"
)
assertTrue(
model.compilationDependsOnSourceSet(iosMainCompilation, commonMain),
"Expected iosMainCompilation depending on commonMain"
)
assertFalse(
model.compilationDependsOnSourceSet(iosMainCompilation, macosMain),
"Expected iosMainCompilation *not* depending on macosMain"
)
assertTrue(
model.compilationDependsOnSourceSet(macosMainCompilation, macosMain),
"Expected macosMainCompilation depending on macosMain"
)
assertTrue(
model.compilationDependsOnSourceSet(macosMainCompilation, appleMain),
"Expected macosMainCompilation depending on appleMain"
)
assertTrue(
model.compilationDependsOnSourceSet(macosMainCompilation, commonMain),
"Expected macosMainCompilation depending on commonMain"
)
assertFalse(
model.compilationDependsOnSourceSet(macosMainCompilation, iosMain),
"Expected macosMainCompilation *not* depending on iosMain"
)
assertTrue(
model.compilationDependsOnSourceSet(metadataCompilation, commonMain),
"Expected metadataCompilation depending on commonMain"
)
assertFalse(
model.compilationDependsOnSourceSet(metadataCompilation, appleMain),
"Expected metadataCompilation *not* depending on appleMain"
)
assertFalse(
model.compilationDependsOnSourceSet(metadataCompilation, macosMain),
"Expected metadataCompilation *not* depending on macosMain"
)
assertFalse(
model.compilationDependsOnSourceSet(metadataCompilation, iosMain),
"Expected metadataCompilation *not* depending on iosMain"
)
}
@Test
fun getCompilations() {
val commonMain = createKotlinSourceSet("commonMain")
val appleMain = createKotlinSourceSet("appleMain", declaredDependsOnSourceSets = setOf("commonMain"))
val macosMain = createKotlinSourceSet("macosMain", declaredDependsOnSourceSets = setOf("appleMain"))
val metadataCompilation = createKotlinCompilation(defaultSourceSets = setOf(commonMain))
val macosMainCompilation = createKotlinCompilation(defaultSourceSets = setOf(macosMain))
val metadataTarget = createKotlinTarget("metadata", compilations = setOf(metadataCompilation))
val macosTarget = createKotlinTarget("macos", compilations = setOf(macosMainCompilation))
val model = createKotlinMPPGradleModel(
sourceSets = setOf(commonMain, appleMain, macosMain),
targets = setOf(metadataTarget, macosTarget)
)
assertEquals(
setOf(metadataCompilation, macosMainCompilation),
model.getCompilations(commonMain),
"Expected correct compilations for commonMain"
)
assertEquals(
setOf(macosMainCompilation), model.getCompilations(appleMain),
"Expected correct compilations for appleMain"
)
assertEquals(
setOf(macosMainCompilation), model.getCompilations(macosMain),
"Expected correct compilations for macosMain"
)
}
}
@@ -0,0 +1,124 @@
/*
* 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 fun createKotlinMPPGradleModel(
dependencyMap: Map<KotlinDependencyId, KotlinDependency> = emptyMap(),
sourceSets: Set<KotlinSourceSet> = emptySet(),
targets: Iterable<KotlinTarget> = emptyList(),
extraFeatures: ExtraFeatures = createExtraFeatures(),
kotlinNativeHome: String = ""
): KotlinMPPGradleModelImpl {
return KotlinMPPGradleModelImpl(
dependencyMap = dependencyMap,
sourceSetsByName = sourceSets.associateBy { it.name },
targets = targets.toList(),
extraFeatures = extraFeatures,
kotlinNativeHome = kotlinNativeHome
)
}
internal fun createExtraFeatures(
coroutinesState: String? = null,
isHmppEnabled: Boolean = false,
isNativeDependencyPropagationEnabled: Boolean = false
): ExtraFeaturesImpl {
return ExtraFeaturesImpl(
coroutinesState = coroutinesState,
isHMPPEnabled = isHmppEnabled,
isNativeDependencyPropagationEnabled = isNativeDependencyPropagationEnabled
)
}
internal fun createKotlinSourceSet(
name: String,
declaredDependsOnSourceSets: Set<String> = emptySet(),
allDependsOnSourceSets: Set<String> = declaredDependsOnSourceSets,
platforms: Set<KotlinPlatform> = emptySet(),
): KotlinSourceSetImpl = KotlinSourceSetImpl(
name = name,
languageSettings = KotlinLanguageSettingsImpl(
languageVersion = null,
apiVersion = null,
isProgressiveMode = false,
enabledLanguageFeatures = emptySet(),
experimentalAnnotationsInUse = emptySet(),
compilerPluginArguments = emptyArray(),
compilerPluginClasspath = emptySet(),
freeCompilerArgs = emptyArray()
),
sourceDirs = emptySet(),
resourceDirs = emptySet(),
dependencies = emptyArray(),
declaredDependsOnSourceSets = declaredDependsOnSourceSets,
allDependsOnSourceSets = allDependsOnSourceSets,
defaultActualPlatforms = KotlinPlatformContainerImpl().apply { pushPlatforms(platforms) },
)
internal fun createKotlinCompilation(
name: String = "main",
defaultSourceSets: Set<KotlinSourceSet> = emptySet(),
allSourceSets: Set<KotlinSourceSet> = emptySet(),
dependencies: Iterable<KotlinDependencyId> = emptyList(),
output: KotlinCompilationOutput = createKotlinCompilationOutput(),
arguments: KotlinCompilationArguments = createKotlinCompilationArguments(),
dependencyClasspath: Iterable<String> = emptyList(),
kotlinTaskProperties: KotlinTaskProperties = createKotlinTaskProperties(),
nativeExtensions: KotlinNativeCompilationExtensions? = null
): KotlinCompilationImpl {
return KotlinCompilationImpl(
name = name,
declaredSourceSets = defaultSourceSets,
allSourceSets = allSourceSets,
dependencies = dependencies.toList().toTypedArray(),
output = output,
arguments = arguments,
dependencyClasspath = dependencyClasspath.toList().toTypedArray(),
kotlinTaskProperties = kotlinTaskProperties,
nativeExtensions = nativeExtensions
)
}
internal fun createKotlinCompilationOutput(): KotlinCompilationOutputImpl {
return KotlinCompilationOutputImpl(
classesDirs = emptySet(),
effectiveClassesDir = null,
resourcesDir = null
)
}
internal fun createKotlinCompilationArguments(): KotlinCompilationArgumentsImpl {
return KotlinCompilationArgumentsImpl(
defaultArguments = emptyArray(),
currentArguments = emptyArray()
)
}
internal fun createKotlinTaskProperties(): KotlinTaskPropertiesImpl {
return KotlinTaskPropertiesImpl(
null, null, null, null
)
}
internal fun createKotlinTarget(
name: String,
platform: KotlinPlatform = KotlinPlatform.COMMON,
compilations: Iterable<KotlinCompilation> = emptyList()
): KotlinTargetImpl {
return KotlinTargetImpl(
name = name,
presetName = null,
disambiguationClassifier = null,
platform = platform,
compilations = compilations.toList(),
testRunTasks = emptyList(),
nativeMainRunTasks = emptyList(),
jar = null,
konanArtifacts = emptyList()
)
}