Resolve dependencies as graph, support multiple modules in a project

This commit is contained in:
Sergey Igushkin
2021-01-18 13:27:25 +03:00
committed by TeamCityServer
parent f1fb438d00
commit d66f95b80e
17 changed files with 516 additions and 350 deletions
@@ -427,8 +427,7 @@ class HierarchicalMppIT : BaseGradleIT() {
shouldInclude: Iterable<Pair<String, String>> = emptyList(),
shouldNotInclude: Iterable<Pair<String, String>> = emptyList()
) {
val taskOutput = getOutputForTask(taskPath.removePrefix(":"))
val compilerArgsLine = taskOutput.lines().single { "Kotlin compiler args:" in it }
val compilerArgsLine = output.lines().single { "$taskPath Kotlin compiler args:" in it }
val classpathItems = compilerArgsLine.substringAfter("-classpath").substringBefore(" -").split(File.pathSeparator)
val actualClasspath = classpathItems.joinToString("\n")
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
@@ -16,44 +17,53 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.currentBuildId
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.matchesModule
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.representsProject
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.getVisibleSourceSetsFromAssociateCompilations
import org.jetbrains.kotlin.project.model.*
class ProjectStructureMetadataModuleBuilder {
private val modulesCache = mutableMapOf<ModuleIdentifier, KotlinModule>()
private val modulesCache = mutableMapOf<KotlinModuleIdentifier, KotlinModule>()
private fun buildModuleFromProjectStructureMetadata(
moduleIdentifier: ModuleIdentifier,
moduleIdentifier: KotlinModuleIdentifier,
metadata: KotlinProjectStructureMetadata
): KotlinModule =
BasicKotlinModule(moduleIdentifier).apply {
metadata.sourceSetNamesByVariantName.keys.forEach { variantName ->
fragments.add(BasicKotlinModuleVariant(this@apply, variantName))
}
fun fragment(sourceSetName: String): BasicKotlinModuleFragment {
if (fragments.none { it.fragmentName == sourceSetName })
fragments.add(BasicKotlinModuleFragment(this@apply, sourceSetName))
return fragmentByName(sourceSetName)
}
metadata.sourceSetNamesByVariantName.forEach { (variantName, sourceSets) ->
val variant = fragmentByName(variantName)
sourceSets.forEach { sourceSetName ->
if (fragments.none { it.fragmentName == sourceSetName })
fragments.add(BasicKotlinModuleFragment(this@apply, sourceSetName))
val fragment = fragmentByName(sourceSetName)
variant.directRefinesDependencies.add(fragment)
variant.directRefinesDependencies.add(fragment(sourceSetName))
}
}
metadata.sourceSetsDependsOnRelation.forEach { (depending, dependencies) ->
val dependingFragment = fragmentByName(depending)
metadata.sourceSetModuleDependencies.forEach { (sourceSetName, dependencies) ->
val fragment = fragment(sourceSetName)
dependencies.forEach { dependency ->
if (fragments.none { it.fragmentName == dependency })
fragments.add(BasicKotlinModuleFragment(this@apply, dependency))
val fragment = fragmentByName(dependency)
dependingFragment.directRefinesDependencies.add(fragment)
fragment.declaredModuleDependencies.add(
KotlinModuleDependency(MavenModuleIdentifier(dependency.groupId.orEmpty(), dependency.moduleId, null /* TODO */))
)
}
}
metadata.sourceSetsDependsOnRelation.forEach { (depending, dependencies) ->
val dependingFragment = fragment(depending)
dependencies.forEach { dependency ->
dependingFragment.directRefinesDependencies.add(fragment(dependency))
}
}
}
fun getModule(moduleIdentifier: ModuleIdentifier, projectStructureMetadata: KotlinProjectStructureMetadata): KotlinModule {
fun getModule(moduleIdentifier: KotlinModuleIdentifier, projectStructureMetadata: KotlinProjectStructureMetadata): KotlinModule {
return modulesCache.getOrPut(moduleIdentifier) {
buildModuleFromProjectStructureMetadata(
moduleIdentifier,
@@ -63,34 +73,81 @@ class ProjectStructureMetadataModuleBuilder {
}
}
private fun detectModules(targets: Iterable<KotlinTarget>, sourceSets: Iterable<KotlinSourceSet>): Map<String, List<KotlinCompilation<*>>> {
// DSU-like approach: all compilations and source sets that are reachable via dependsOn edges are considered a single module
val compilations = targets.flatMap { it.compilations }
val dsu = mutableMapOf<Any, Any>().apply {
compilations.forEach { put(it, it) }
sourceSets.forEach { put(it, it) }
}
fun get(item: Any): Any =
dsu.getValue(item).let { leader -> if (leader === item) leader else get(leader).also { dsu[item] = it } }
fun union(item: Any, other: Any) = dsu.put(get(item), get(other))
sourceSets.forEach { sourceSet ->
sourceSet.dependsOn.forEach { other -> union(sourceSet, other) }
}
compilations.forEach { compilation ->
compilation.kotlinSourceSets.forEach { union(compilation, it) }
}
val uniqueCompilationNamesCounter = mutableMapOf<Set<String>, Int>()
fun moduleName(compilations: Iterable<KotlinCompilation<*>>): String {
val names = compilations.map { it.name }.toSortedSet()
val uniqueNumber = uniqueCompilationNamesCounter.put(names, uniqueCompilationNamesCounter[names]?.plus(1) ?: 0)
return names.joinToString("-") + (uniqueNumber?.let { "-$it" } ?: "")
}
return compilations.groupBy { get(it) }.values.associateBy { moduleName(it) }
}
@Suppress("unused")
class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExplicit: Boolean) {
fun buildModuleFromProject(project: Project): KotlinModule? {
fun buildModulesFromProject(project: Project): List<KotlinModule> {
val extension = project.multiplatformExtensionOrNull
?: project.kotlinExtension
?: return null
?: return emptyList()
val targets = when (extension) {
is KotlinMultiplatformExtension -> extension.targets.filter { it.name != "metadata" }
is KotlinMultiplatformExtension -> extension.targets.filter { it.name != KotlinMultiplatformPlugin.METADATA_TARGET_NAME }
is KotlinSingleTargetExtension -> listOf(extension.target)
else -> return null
else -> return emptyList()
}
return BasicKotlinModule(LocalModuleIdentifier(project.currentBuildId().name, project.path)).apply {
val variantToCompilation = mutableMapOf<KotlinModuleFragment, KotlinCompilation<*>>()
val moduleCompilationCluster = detectModules(targets, extension.sourceSets)
targets.forEach { target ->
val publishedVariantsByCompilation = (target as? AbstractKotlinTarget)?.kotlinComponents.orEmpty()
.flatMap { component -> (component as? KotlinVariant)?.usages.orEmpty() }
.groupBy { it.compilation }
val publishedVariantsByCompilation = targets.flatMap { target ->
(target as? AbstractKotlinTarget)?.kotlinComponents.orEmpty()
.flatMap { component -> (component as? KotlinVariant)?.usages.orEmpty() }
}.groupBy { it.compilation }
target.compilations.forEach { compilation ->
val names =
publishedVariantsByCompilation[compilation].orEmpty()
.filter { it.includeIntoProjectStructureMetadata }.map { it.name }
val moduleByFragment = mutableMapOf<KotlinModuleFragment, KotlinModule>()
names.forEach { variantName ->
val result = moduleCompilationCluster.entries.map { (classifier, compilationsToInclude) ->
val sourceSetsToInclude = compilationsToInclude.flatMapTo(mutableSetOf()) { it.allKotlinSourceSets }
val moduleIdentifier = LocalModuleIdentifier(
project.currentBuildId().name,
project.path,
classifier.takeIf { it != KotlinCompilation.MAIN_COMPILATION_NAME }
)
BasicKotlinModule(moduleIdentifier).apply {
val variantToCompilation = mutableMapOf<BasicKotlinModuleFragment, KotlinCompilation<*>>()
compilationsToInclude.forEach { compilation ->
// A compilation may be exposed as more than one variant, so we collect all of its names
val variantNames =
publishedVariantsByCompilation[compilation]?.filter { it.includeIntoProjectStructureMetadata }?.map { it.name }
?: listOf(compilation.defaultSourceSetName)
variantNames.forEach { variantName ->
val variant = BasicKotlinModuleVariant(this@apply, variantName)
moduleByFragment[variant] = this@apply
variantToCompilation[variant] = compilation
fragments.add(variant)
@@ -104,50 +161,53 @@ class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExp
variant.isExported = compilation.isMain()
}
}
}
extension.sourceSets.forEach { sourceSet ->
val existingVariant = fragments.filterIsInstance<BasicKotlinModuleVariant>().find { it.fragmentName == sourceSet.name }
val fragment = existingVariant ?: BasicKotlinModuleFragment(this@apply, sourceSet.name).also { fragments.add(it) }
fragment.kotlinSourceRoots = sourceSet.kotlin.sourceDirectories.toList()
// Once all fragments are created, add dependencies between them
sourceSetsToInclude.forEach { sourceSet ->
val existingVariant = fragments.filterIsInstance<BasicKotlinModuleVariant>().find { it.fragmentName == sourceSet.name }
val fragment = existingVariant ?: BasicKotlinModuleFragment(this@apply, sourceSet.name).also { fragments.add(it) }
moduleByFragment[fragment] = this@apply
fragment.kotlinSourceRoots = sourceSet.kotlin.sourceDirectories.toList()
// FIXME: Kotlin/Native implementation-effective-api dependencies are missing here. Introduce dependency scopes
project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies.forEach {
val moduleDependency = it.toModuleDependency(project)
fragment.declaredModuleDependencies.add(moduleDependency)
}
}
// Once all fragments are created, add dependencies between them
fragments.forEach { fragment ->
val sourceSet = extension.sourceSets.findByName(fragment.fragmentName)
?: variantToCompilation.getValue(fragment).defaultSourceSet
sourceSet.dependsOn.forEach { dependency ->
val dependencyFragment = fragmentByName(dependency.name)
fragment.directRefinesDependencies.add(dependencyFragment)
}
}
targets.forEach { target ->
target.compilations.forEach { compilation ->
val fragment = fragmentByName(compilation.defaultSourceSetName)
compilation.associateWith.forEach { associate ->
val dependencyFragment = fragmentByName(associate.defaultSourceSetName)
fragment.declaredContainingModuleFragmentDependencies.add(dependencyFragment)
// FIXME: Kotlin/Native implementation-effective-api dependencies are missing here. Introduce dependency scopes
requestedDependencies(project, sourceSet, listOf(KotlinDependencyScope.API_SCOPE)).forEach {
val moduleDependency = it.toModuleDependency(project)
fragment.declaredModuleDependencies.add(moduleDependency)
}
}
}
if (addInferredSourceSetVisibilityAsExplicit) {
extension.sourceSets.forEach { sourceSet ->
val fragment = fragmentByName(sourceSet.name)
getVisibleSourceSetsFromAssociateCompilations(project, sourceSet).forEach { dependency ->
fragments.forEach { fragment ->
val sourceSet = extension.sourceSets.findByName(fragment.fragmentName)
?: variantToCompilation.getValue(fragment).defaultSourceSet
sourceSet.dependsOn.forEach { dependency ->
val dependencyFragment = fragmentByName(dependency.name)
fragment.declaredContainingModuleFragmentDependencies.add(dependencyFragment)
fragment.directRefinesDependencies.add(dependencyFragment)
}
}
}
}
}
fun fragmentByName(name: String) =
result.asSequence().flatMap { it.fragments.asSequence() }.first { it.fragmentName == name }
targets.flatMap { it.compilations }.forEach { compilation ->
val variant = fragmentByName(compilation.defaultSourceSetName)
compilation.associateWith.forEach { associate ->
val associateVariant = fragmentByName(associate.defaultSourceSetName)
variant.declaredModuleDependencies.add(KotlinModuleDependency(associateVariant.containingModule.moduleIdentifier))
}
}
if (addInferredSourceSetVisibilityAsExplicit) {
project.kotlinExtension.sourceSets.forEach { sourceSet ->
val fragment = fragmentByName(sourceSet.name)
getVisibleSourceSetsFromAssociateCompilations(project, sourceSet).forEach { dependency ->
val dependencyFragment = fragmentByName(dependency.name)
fragment.declaredModuleDependencies.add(KotlinModuleDependency(dependencyFragment.containingModule.moduleIdentifier))
}
}
}
return result
}
private fun <T : Any> attributeString(container: AttributeContainer, attributeKey: Attribute<T>): String {
val value = container.getAttribute(attributeKey)
@@ -158,13 +218,35 @@ class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExp
}
}
private fun <T : Any> attributeString(container: AttributeContainer, attributeKey: Attribute<T>): String {
val value = container.getAttribute(attributeKey)
return when (value) {
is Named -> value.name
else -> value.toString()
}
}
internal fun Dependency.toModuleDependency(
project: Project
) = when (val dependency = this) {
is ProjectDependency ->
ModuleDependency(LocalModuleIdentifier(project.currentBuildId().name, dependency.dependencyProject.path))
else ->
ModuleDependency(MavenModuleIdentifier(dependency.group.orEmpty(), dependency.name))
): KotlinModuleDependency {
return KotlinModuleDependency(
when (this) {
is ProjectDependency ->
LocalModuleIdentifier(
project.currentBuildId().name,
dependencyProject.path,
moduleClassifiersFromCapabilities(requestedCapabilities).single() // FIXME multiple capabilities
)
is ModuleDependency ->
MavenModuleIdentifier(
group.orEmpty(),
name,
moduleClassifiersFromCapabilities(requestedCapabilities).single() // FIXME multiple capabilities
)
else -> MavenModuleIdentifier(group.orEmpty(), name, null)
}
)
}
private fun BasicKotlinModule.fragmentByName(name: String) =
@@ -194,7 +276,10 @@ class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver
.flatMap { it.usages }
.firstOrNull { it.name == requestingVariant.fragmentName }
?.compilation
?: return VariantResolution.NotRequested(requestingVariant, dependencyModule)
?: targets.asSequence().flatMap { it.compilations.asSequence() }.single {
it.defaultSourceSetName == requestingVariant.fragmentName
} // TODO: generalize the mapping PM2.0 <-> MPP
?: return VariantResolution.Unknown(requestingVariant, dependencyModule)
val compileClasspath = project.configurations.getByName(compilation.compileDependencyConfigurationName)
@@ -18,6 +18,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_RUNTIME_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import java.io.File
import java.io.InputStream
import java.util.*
@@ -85,7 +87,6 @@ internal class GranularMetadataTransformation(
/** A list of scopes that the dependencies from [kotlinSourceSet] are treated as requested dependencies. */
private val sourceSetRequestedScopes: List<KotlinDependencyScope>,
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
private val allSourceSetsConfiguration: Configuration,
private val parentTransformations: Lazy<Iterable<GranularMetadataTransformation>>
) {
val metadataDependencyResolutions: Iterable<MetadataDependencyResolution> by lazy { doTransform() }
@@ -98,40 +99,14 @@ internal class GranularMetadataTransformation(
)
private val requestedDependencies: Iterable<Dependency> by lazy {
fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set<Dependency> =
sourceSetRequestedScopes.flatMapTo(mutableSetOf()) { scope ->
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).incoming.dependencies
}
val ownDependencies = collectScopedDependenciesFromSourceSet(kotlinSourceSet)
val parentDependencies = parentTransformations.value.flatMapTo(mutableSetOf<Dependency>()) { it.requestedDependencies }
ownDependencies + parentDependencies
requestedDependencies(project, kotlinSourceSet, sourceSetRequestedScopes)
}
private val allSourceSetsConfiguration: Configuration =
commonMetadataDependenciesConfigurationForScopes(project, sourceSetRequestedScopes)
private val configurationToResolve: Configuration by lazy {
/** If [kotlinSourceSet] is not a published source set, its dependencies are not included in [allSourceSetsConfiguration].
* In that case, to resolve the dependencies of the source set in a way that is consistent with the published source sets,
* we need to create a new configuration with the dependencies from both [allSourceSetsConfiguration] and the
* input configuration(s) of the source set. */
var modifiedConfiguration: Configuration? = null
val originalDependencies = allSourceSetsConfiguration.allDependencies
requestedDependencies.forEach { dependency ->
if (dependency !in originalDependencies) {
modifiedConfiguration = modifiedConfiguration ?: project.configurations.detachedConfiguration().apply {
fun <T> copyAttribute(key: Attribute<T>) {
attributes.attribute(key, allSourceSetsConfiguration.attributes.getAttribute(key)!!)
}
allSourceSetsConfiguration.attributes.keySet().forEach { copyAttribute(it) }
allSourceSetsConfiguration.extendsFrom.forEach { extendsFrom(it) }
}
modifiedConfiguration!!.dependencies.add(dependency)
}
}
modifiedConfiguration ?: allSourceSetsConfiguration
resolvableMetadataConfiguration(project, allSourceSetsConfiguration, requestedDependencies)
}
private fun doTransform(): Iterable<MetadataDependencyResolution> {
@@ -229,7 +204,7 @@ internal class GranularMetadataTransformation(
project,
module,
configurationToResolve,
resolveViaAvailableAt = false // we will process this dependency later in the queue
resolveViaAvailableAt = false // we will process the available-at module as a dependency later in the queue
)
val resolvedToProject: Project? = (mppDependencyMetadataExtractor as? ProjectMppDependencyMetadataExtractor)?.dependencyProject
@@ -301,6 +276,75 @@ internal class GranularMetadataTransformation(
}
}
internal fun resolvableMetadataConfiguration(
project: Project,
sourceSets: Iterable<KotlinSourceSet>,
scopes: Iterable<KotlinDependencyScope>
) = resolvableMetadataConfiguration(
project,
commonMetadataDependenciesConfigurationForScopes(project, scopes),
sourceSets.flatMapTo(mutableListOf()) { requestedDependencies(project, it, scopes) }
)
/** If a source set is not a published source set, its dependencies are not included in [allSourceSetsConfiguration].
* In that case, to resolve the dependencies of the source set in a way that is consistent with the published source sets,
* we need to create a new configuration with the dependencies from both [allSourceSetsConfiguration] and the
* other [requestedDependencies] */
// TODO: optimize by caching the resulting configurations?
internal fun resolvableMetadataConfiguration(
project: Project,
allSourceSetsConfiguration: Configuration,
requestedDependencies: Iterable<Dependency>
): Configuration {
var modifiedConfiguration: Configuration? = null
val originalDependencies = allSourceSetsConfiguration.allDependencies
requestedDependencies.forEach { dependency ->
if (dependency !in originalDependencies) {
modifiedConfiguration = modifiedConfiguration ?: project.configurations.detachedConfiguration().apply {
fun <T> copyAttribute(key: Attribute<T>) {
attributes.attribute(key, allSourceSetsConfiguration.attributes.getAttribute(key)!!)
}
allSourceSetsConfiguration.attributes.keySet().forEach { copyAttribute(it) }
allSourceSetsConfiguration.extendsFrom.forEach { extendsFrom(it) }
dependencies.addAll(originalDependencies) // TODO: check if this line is redundant
}
modifiedConfiguration!!.dependencies.add(dependency)
}
}
return modifiedConfiguration ?: allSourceSetsConfiguration
}
/** The configuration that contains the dependencies of the corresponding scopes (and maybe others)
* from all published source sets. */
internal fun commonMetadataDependenciesConfigurationForScopes(
project: Project,
scopes: Iterable<KotlinDependencyScope>
): Configuration {
// TODO: what if 'runtimeOnly' is combined with 'compileOnly'? prohibit this or merge the two? we never do that now, though
val configurationName = if (KotlinDependencyScope.RUNTIME_ONLY_SCOPE in scopes)
ALL_RUNTIME_METADATA_CONFIGURATION_NAME
else
ALL_COMPILE_METADATA_CONFIGURATION_NAME
return project.configurations.getByName(configurationName)
}
internal fun requestedDependencies(
project: Project,
sourceSet: KotlinSourceSet,
requestedScopes: Iterable<KotlinDependencyScope>
): Iterable<Dependency> {
fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set<Dependency> =
requestedScopes.flatMapTo(mutableSetOf()) { scope ->
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).incoming.dependencies
}
val otherContributingSourceSets = dependsOnClosureWithInterCompilationDependencies(project, sourceSet)
return listOf(sourceSet, *otherContributingSourceSets.toTypedArray()).flatMap(::collectScopedDependenciesFromSourceSet)
}
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
@@ -343,7 +387,7 @@ private class IncludedBuildMetadataExtractor(
init {
val id = dependency.id
require(id is ProjectComponentIdentifier) { "dependency should resolve to a project" }
require(!(id as ProjectComponentIdentifier).build.isCurrentBuild) { "should be a project from an included build" }
require(!id.build.isCurrentBuild) { "should be a project from an included build" }
this.id = id
}
@@ -493,7 +537,6 @@ internal fun getProjectStructureMetadata(
module: ResolvedComponentResult,
configuration: Configuration
): KotlinProjectStructureMetadata? {
// FIXME test dependencies won't work
val extractor = getMetadataExtractor(project, module, configuration, resolveViaAvailableAt = true)
return extractor?.getProjectStructureMetadata()
}
@@ -17,7 +17,9 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.withAllDependsOnSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
import org.jetbrains.kotlin.gradle.targets.metadata.isSharedNativeSourceSet
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
@@ -114,14 +116,19 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec
* published as API dependencies of the metadata module to get into the resolution result, see
* [KotlinMetadataTargetConfigurator.exportDependenciesForPublishing].
*/
val isNativeSharedSourceSet = sourceSetsWithMetadataCompilations[sourceSet] is KotlinSharedNativeCompilation
val sourceSetExportedDependencies = when {
isNativeSharedSourceSet -> sourceSet.withAllDependsOnSourceSets().flatMap { hierarchySourceSet ->
listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE).flatMap { scope ->
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope).allDependencies.toList()
}
}.distinct()
else -> project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies
val isNativeSharedSourceSet = isSharedNativeSourceSet(project, sourceSet)
val scopes = listOfNotNull(
KotlinDependencyScope.API_SCOPE,
KotlinDependencyScope.IMPLEMENTATION_SCOPE.takeIf { isNativeSharedSourceSet }
)
val sourceSetsToIncludeDependencies =
if (isNativeSharedSourceSet)
dependsOnClosureWithInterCompilationDependencies(project, sourceSet).plus(sourceSet)
else listOf(sourceSet)
val sourceSetExportedDependencies = scopes.flatMap { scope ->
sourceSetsToIncludeDependencies.flatMap { hierarchySourceSet ->
project.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope).allDependencies.toList()
}
}
sourceSet.name to sourceSetExportedDependencies.map { ModuleIds.fromDependency(it) }.toSet()
},
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.*
import org.jetbrains.kotlin.gradle.plugin.sources.withAllDependsOnSourceSets
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetConfigurator
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File
import javax.inject.Inject
@@ -77,9 +78,8 @@ open class TransformKotlinGranularMetadata
project,
kotlinSourceSet,
listOf(API_SCOPE, IMPLEMENTATION_SCOPE, COMPILE_ONLY_SCOPE),
project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME),
lazy {
KotlinMetadataTargetConfigurator.dependsOnWithInterCompilationDependencies(project, kotlinSourceSet).map {
dependsOnClosureWithInterCompilationDependencies(project, kotlinSourceSet).map {
project.tasks.withType(TransformKotlinGranularMetadata::class.java)
.getByName(KotlinMetadataTargetConfigurator.transformGranularMetadataTaskName(it.name))
.transformation
@@ -97,7 +97,7 @@ open class TransformKotlinGranularMetadata
private val extractableFilesByResolution: Map<out MetadataDependencyResolution, ExtractableMetadataFiles>
get() = metadataDependencyResolutions
.filterIsInstance<MetadataDependencyResolution.ChooseVisibleSourceSets>()
.associate { it to it.getExtractableMetadataFiles(outputsDir) }
.associateWith { it.getExtractableMetadataFiles(outputsDir) }
@get:Internal
internal val filesByResolution: Map<out MetadataDependencyResolution, FileCollection>
@@ -338,6 +338,10 @@ internal object CompilationSourceSetUtil {
return ext.get(EXT_NAME) as Property<CompilationsBySourceSet>
}
// FIXME: the results include the compilations of the metadata target; the callers should care about filtering them out
// if they need only the platform compilations
// TODO: create a separate util function: `platformCompilationsBySourceSets`
// TODO: visit all call sites, check if they handle the metadata compilations correctly
fun compilationsBySourceSets(project: Project): CompilationsBySourceSet {
val compilationNamesBySourceSetName = getOrCreateProperty(project) {
var shouldFinalizeValue = false
@@ -0,0 +1,67 @@
/*
* 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.plugin.mpp.pm20
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.project.model.*
class GradleKotlinDependencyGraphResolver(
private val project: Project,
private val moduleResolver: ModuleDependencyResolver,
private val fragmentsResolver: ModuleFragmentsResolver
) : KotlinDependencyGraphResolver {
private val configurationToResolve: Configuration
get() = resolvableMetadataConfiguration(
project,
project.kotlinExtension.sourceSets, // take dependencies from all source sets; TODO introduce consistency scopes?
KotlinDependencyScope.compileScopes
)
override fun resolveDependencyGraph(requestingModule: KotlinModule): DependencyGraphResolution {
if (!requestingModule.representsProject(project))
return DependencyGraphResolution.Unknown(requestingModule)
val excludeLegacyMetadataModulesFromResult = mutableSetOf<ResolvedComponentResult>()
val nodeByModuleId = mutableMapOf<KotlinModuleIdentifier, DependencyGraphNode>()
fun nodeFromComponent(component: ResolvedComponentResult, isRoot: Boolean /*refactor*/): DependencyGraphNode {
val id = component.toModuleIdentifier()
return nodeByModuleId.getOrPut(id) {
val module = if (isRoot)
requestingModule
else moduleResolver.resolveDependency(component.toModuleDependency())
.takeIf { component !in excludeLegacyMetadataModulesFromResult }
?: buildStubModule(component, component.variants.singleOrNull()?.displayName ?: "default")
val resolvedComponentDependencies = component.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.flatMap { dependency -> dependency.requested.toModuleIdentifiers().map { id -> id to dependency.selected } }
.toMap()
val fragmentDependencies = module.fragments.associateWith { it.declaredModuleDependencies }
val nodeDependenciesMap = fragmentDependencies.mapValues { (_, deps) ->
deps.mapNotNull { resolvedComponentDependencies[it.moduleIdentifier] }.map { nodeFromComponent(it, isRoot = false) }
}
DependencyGraphNode(module, nodeDependenciesMap)
}
}
return DependencyGraphResolution.DependencyGraph(
requestingModule,
nodeFromComponent(configurationToResolve.incoming.resolutionResult.root, isRoot = true)
)
}
}
@@ -7,16 +7,17 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.pm20
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.component.*
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.capabilities.Capability
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.ProjectStructureMetadataModuleBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.GradleProjectModuleBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.project.model.*
import java.util.ArrayDeque
@@ -26,21 +27,29 @@ class GradleModuleDependencyResolver(
private val projectModuleBuilder: GradleProjectModuleBuilder
) : ModuleDependencyResolver {
// FIXME this wont work for tests, as their dependencies are currently not in the all compile dependencies configuration
private val configurationToResolve: Configuration
get() = project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)
get() = resolvableMetadataConfiguration(
project,
project.kotlinExtension.sourceSets, // take dependencies from all source sets; TODO introduce consistency scopes?
listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE, KotlinDependencyScope.COMPILE_ONLY_SCOPE)
)
override fun resolveDependency(moduleDependency: ModuleDependency): KotlinModule? {
override fun resolveDependency(moduleDependency: KotlinModuleDependency): KotlinModule? {
val allComponents = configurationToResolve.incoming.resolutionResult.allComponents
// TODO: optimize O(n) search, store the resolved components with dependency keys
// TODO: optimize O(n) search, store the resolved components with dependency keys?
val component = allComponents.find { it.id.matchesModuleDependency(moduleDependency) }
val id = component?.id
//FIXME multiple?
val classifier = moduleClassifiersFromCapabilities(component?.variants?.flatMap { it.capabilities }.orEmpty()).single()
return when {
id is ProjectComponentIdentifier && id.build.isCurrentBuild ->
projectModuleBuilder.buildModuleFromProject(project.project(id.projectPath))
projectModuleBuilder.buildModulesFromProject(project.project(id.projectPath))
.find { it.moduleIdentifier.moduleClassifier == classifier }
id is ModuleComponentIdentifier -> {
val metadata = getProjectStructureMetadata(project, component, configurationToResolve) ?: return null
projectStructureMetadataModuleBuilder.getModule(id.toModuleIdentifier(), metadata)
projectStructureMetadataModuleBuilder.getModule(id.toModuleIdentifier(classifier), metadata)
}
else -> null
}
@@ -52,7 +61,7 @@ class GradleProjectDependencyDiscovery(
private val variantDependencyDiscovery: VariantDependencyDiscovery,
private val fragmentDependenciesDiscovery: FragmentDependenciesDiscovery
) : DependencyDiscovery {
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<KotlinModuleDependency> {
require(fragment.containingModule.representsProject(project))
return when (fragment) {
is KotlinModuleVariant -> variantDependencyDiscovery.discoverDependencies(fragment)
@@ -63,63 +72,88 @@ class GradleProjectDependencyDiscovery(
class FragmentDependenciesDiscovery(
private val project: Project,
private val expansion: InternalDependencyExpansion,
private val moduleResolver: ModuleDependencyResolver,
private val fragmentsResolver: KotlinModuleFragmentsResolver
private val fragmentsResolver: ModuleFragmentsResolver
) : DependencyDiscovery {
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
private val dependencyScopes = listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE)
private fun KotlinModuleFragment.toSourceSet(): KotlinSourceSet = project.kotlinExtension.sourceSets.getByName(fragmentName)
// TODO: use the dependency graph resolution implementation?
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<KotlinModuleDependency> {
require(fragment.containingModule.representsProject(project))
val requested = expansion.expandInternalFragmentDependencies(fragment).visibleFragments().plus(fragment.refinesClosure)
.flatMap { it.declaredModuleDependencies }
.toSet()
val fragmentsWhoseDependenciesAreVisible = fragment.refinesClosure
// FIXME: test dependencies won't work here
val allComponents =
project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME).incoming.resolutionResult.allComponents
// FIXME use dependencyScopes, not just sets of declared module dependencies
val requestedDependencies = fragmentsWhoseDependenciesAreVisible.flatMap { it.declaredModuleDependencies }.toSet()
val componentByModuleDependency = allComponents.associateBy { it.toModuleDependency() }
val configurationToResolve = resolvableMetadataConfiguration(
project,
fragmentsWhoseDependenciesAreVisible.map { it.toSourceSet() },
KotlinDependencyScope.compileScopes
)
val allComponents = configurationToResolve.incoming.resolutionResult.allComponents
val componentsByRequestedDependency =
allComponents.flatMap { component -> component.dependents.map { it to component } }.toMap()
.mapKeys { it.key.requested.toModuleDependency() }
val resolvedComponentQueue = ArrayDeque<ResolvedComponentResult>().apply {
addAll(componentByModuleDependency.filterKeys { it in requested }.values)
addAll(componentsByRequestedDependency.filterKeys { it in requestedDependencies }.values)
}
val visited = mutableSetOf<ResolvedComponentResult>()
val excludeLegacyMetadataModulesFromResult = mutableSetOf<ResolvedComponentResult>()
while (resolvedComponentQueue.isNotEmpty()) {
val component = resolvedComponentQueue.removeFirst()
visited.add(component)
val module = moduleResolver.resolveDependency(component.toModuleDependency())
.takeIf { component !in excludeLegacyMetadataModulesFromResult }
?: buildStubModule(component, component.variants.singleOrNull()?.displayName ?: "default")
val newTransitiveDeps = when (val fragmentsResolution = fragmentsResolver.getChosenFragments(fragment, module)) {
val transitiveDepsToVisit = when (val fragmentsResolution = fragmentsResolver.getChosenFragments(fragment, module)) {
is FragmentResolution.ChosenFragments ->
fragmentsResolution.visibleFragments.flatMap { it.declaredModuleDependencies }.mapNotNull { dependency ->
componentByModuleDependency[dependency].takeIf { component -> component !in visited }
componentsByRequestedDependency[dependency]
}
else -> emptyList()
}
val newTransitiveDeps = transitiveDepsToVisit.filterTo(mutableListOf()) { it !in visited }
// With legacy publication scheme, the root MPP module may have a single 'dependency' (available-at) to the metadata module
val isMetadataModulePublishedSeparately =
getProjectStructureMetadata(project, component, configurationToResolve)
?.let { !it.isPublishedAsRoot }
?: false
val singleDependencyComponentOrNull = (component.dependencies.singleOrNull() as? ResolvedDependencyResult)?.selected
if (isMetadataModulePublishedSeparately && singleDependencyComponentOrNull != null) {
newTransitiveDeps.add(singleDependencyComponentOrNull)
excludeLegacyMetadataModulesFromResult.add(singleDependencyComponentOrNull)
}
resolvedComponentQueue.addAll(newTransitiveDeps)
}
return visited.map { it.toModuleDependency() }
return (visited - excludeLegacyMetadataModulesFromResult).map { it.toModuleDependency() }
}
}
// refactor extract to a separate class
// TODO think about multi-variant stub modules for non-Kotlin modules which got more than one chosen variant
private fun buildStubModule(resolvedComponentResult: ResolvedComponentResult, singleVariantName: String): KotlinModule {
val moduleDependency = resolvedComponentResult.toModuleDependency()
return BasicKotlinModule(moduleDependency.moduleIdentifier).apply {
BasicKotlinModuleVariant(this@apply, singleVariantName).apply {
fragments.add(this)
this.declaredModuleDependencies.addAll(
resolvedComponentResult.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { it.selected.toModuleDependency() }
)
}
// refactor extract to a separate class/interface
// TODO think about multi-variant stub modules for non-Kotlin modules which got more than one chosen variant
internal fun buildStubModule(resolvedComponentResult: ResolvedComponentResult, singleVariantName: String): KotlinModule {
val moduleDependency = resolvedComponentResult.toModuleDependency()
return BasicKotlinModule(moduleDependency.moduleIdentifier).apply {
BasicKotlinModuleVariant(this@apply, singleVariantName).apply {
fragments.add(this)
this.declaredModuleDependencies.addAll(
resolvedComponentResult.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { it.selected.toModuleDependency() }
)
}
}
}
@@ -142,24 +176,28 @@ class VariantDependencyDiscovery(
return configuration.incoming.resolutionResult.allComponents
}
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<KotlinModuleDependency> {
require(fragment is KotlinModuleVariant)
require(fragment.containingModule.representsProject(project))
val components = resolvedComponentResults(fragment)
return components.mapNotNull { component ->
val moduleIdentifier = when (val id = component.id) {
is ProjectComponentIdentifier -> LocalModuleIdentifier(id.build.name, id.projectPath)
is ModuleComponentIdentifier -> id.toModuleIdentifier()
else -> return@mapNotNull null // TODO check that no other options are possible, throw errors?
return components.flatMap { component ->
val classifiers = moduleClassifiersFromCapabilities(component.variants.flatMap { it.capabilities })
when (val id = component.id) {
is ProjectComponentIdentifier -> {
classifiers.map { LocalModuleIdentifier(id.build.name, id.projectPath, it) }
}
is ModuleComponentIdentifier -> {
classifiers.map { id.toModuleIdentifier(it) }
}
else -> return@flatMap emptyList<KotlinModuleIdentifier>() // TODO check that no other options are possible, throw errors?
}
ModuleDependency(moduleIdentifier)
}
}.map(::KotlinModuleDependency)
}
}
private fun ModuleComponentIdentifier.toModuleIdentifier(): MavenModuleIdentifier =
MavenModuleIdentifier(moduleIdentifier.group, moduleIdentifier.name)
private fun ModuleComponentIdentifier.toModuleIdentifier(classifier: String? = null): MavenModuleIdentifier =
MavenModuleIdentifier(moduleIdentifier.group, moduleIdentifier.name, classifier)
internal fun ComponentIdentifier.matchesModule(module: KotlinModule): Boolean {
return when (val moduleId = module.moduleIdentifier) {
@@ -175,15 +213,37 @@ internal fun ComponentIdentifier.matchesModule(module: KotlinModule): Boolean {
}
}
internal fun ResolvedComponentResult.toModuleDependency(): ModuleDependency = ModuleDependency(
when (val id = id) {
is ProjectComponentIdentifier -> LocalModuleIdentifier(id.build.name, id.projectPath)
internal fun ResolvedComponentResult.toModuleIdentifier(): KotlinModuleIdentifier {
val capabilities = variants.flatMap { it.capabilities } // expected to be disjoint
val moduleClassifier = moduleClassifiersFromCapabilities(capabilities).single() // FIXME handle multiple capabilities
return when (val id = id) {
is ProjectComponentIdentifier -> LocalModuleIdentifier(id.build.name, id.projectPath, moduleClassifier)
is ModuleComponentIdentifier -> id.toModuleIdentifier()
else -> MavenModuleIdentifier(moduleVersion?.group.orEmpty(), moduleVersion?.name.orEmpty())
else -> MavenModuleIdentifier(moduleVersion?.group.orEmpty(), moduleVersion?.name.orEmpty(), moduleClassifier)
}
)
}
internal fun ComponentIdentifier.matchesModuleDependency(moduleDependency: ModuleDependency) =
internal fun moduleClassifiersFromCapabilities(capabilities: Iterable<Capability>): Iterable<String?> {
val classifierCapabilities = capabilities.filter { it.name.contains("..") }
return if (classifierCapabilities.none()) listOf(null) else classifierCapabilities.map { it.name.substringAfterLast("..") /*FIXME invent a more stable scheme*/ }
}
internal fun ComponentSelector.toModuleIdentifiers(): Iterable<KotlinModuleIdentifier> {
val moduleClassifiers = moduleClassifiersFromCapabilities(requestedCapabilities)
return when (this) {
is ProjectComponentSelector -> moduleClassifiers.map { LocalModuleIdentifier(buildName, projectPath, it) }
is ModuleComponentSelector -> moduleClassifiers.map { MavenModuleIdentifier(moduleIdentifier.group, moduleIdentifier.name, it) }
else -> error("unexpected component selector")
}
}
internal fun ResolvedComponentResult.toModuleDependency(): KotlinModuleDependency = KotlinModuleDependency(toModuleIdentifier())
internal fun ComponentSelector.toModuleDependency(): KotlinModuleDependency {
val moduleId = toModuleIdentifiers().single() // FIXME handle multiple
return KotlinModuleDependency(moduleId)
}
internal fun ComponentIdentifier.matchesModuleDependency(moduleDependency: KotlinModuleDependency) =
when (val id = moduleDependency.moduleIdentifier) {
is LocalModuleIdentifier -> {
val projectId = this as? ProjectComponentIdentifier
@@ -19,7 +19,11 @@ internal enum class KotlinDependencyScope(val scopeName: String) {
API_SCOPE(API),
IMPLEMENTATION_SCOPE(IMPLEMENTATION),
COMPILE_ONLY_SCOPE(COMPILE_ONLY),
RUNTIME_ONLY_SCOPE(RUNTIME_ONLY)
RUNTIME_ONLY_SCOPE(RUNTIME_ONLY);
companion object {
val compileScopes = listOf(KotlinDependencyScope.API_SCOPE, KotlinDependencyScope.IMPLEMENTATION_SCOPE, KotlinDependencyScope.COMPILE_ONLY_SCOPE)
}
}
internal fun Project.sourceSetDependencyConfigurationByScope(sourceSet: KotlinSourceSet, scope: KotlinDependencyScope): Configuration =
@@ -18,6 +18,7 @@ import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
@@ -53,11 +54,6 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
internal fun transformGranularMetadataTaskName(sourceSetName: String) =
lowerCamelCaseName("transform", sourceSetName, "DependenciesMetadata")
internal fun dependsOnWithInterCompilationDependencies(project: Project, sourceSet: KotlinSourceSet): Set<KotlinSourceSet> =
sourceSet.dependsOn.toMutableSet().apply {
addAll(getVisibleSourceSetsFromAssociateCompilations(project, sourceSet))
}
}
override fun configureTarget(target: KotlinMetadataTarget) {
@@ -292,11 +288,10 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
val compilationName = sourceSet.name
val platformCompilations = compilationsBySourceSets(project).getValue(sourceSet)
val platformCompilations = compilationsBySourceSets(project)
.getValue(sourceSet).filter { it.target.name != KotlinMultiplatformPlugin.METADATA_TARGET_NAME }
val isNativeSourceSet = platformCompilations.all { compilation ->
compilation.target is KotlinNativeTarget || compilation is KotlinSharedNativeCompilation
}
val isNativeSourceSet = isSharedNativeSourceSet(project, sourceSet)
val compilationFactory: KotlinCompilationFactory<out AbstractKotlinCompilation<*>> = when {
isNativeSourceSet -> KotlinSharedNativeCompilationFactory(
@@ -362,20 +357,12 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
isSourceSetPublished: Boolean
) {
KotlinDependencyScope.values().forEach { scope ->
val allMetadataConfiguration = project.configurations.getByName(
when (scope) {
KotlinDependencyScope.RUNTIME_ONLY_SCOPE -> ALL_RUNTIME_METADATA_CONFIGURATION_NAME
else -> ALL_COMPILE_METADATA_CONFIGURATION_NAME
}
)
val granularMetadataTransformation = GranularMetadataTransformation(
project,
sourceSet,
listOf(scope),
allMetadataConfiguration,
lazy {
dependsOnWithInterCompilationDependencies(project, sourceSet).filterIsInstance<DefaultKotlinSourceSet>()
dependsOnClosureWithInterCompilationDependencies(project, sourceSet).filterIsInstance<DefaultKotlinSourceSet>()
.map { checkNotNull(it.dependencyTransformations[scope]) }
}
)
@@ -565,6 +552,20 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
}
}
internal fun isSharedNativeSourceSet(project: Project, sourceSet: KotlinSourceSet): Boolean {
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project)[sourceSet].orEmpty()
return compilations.isNotEmpty() && compilations.all {
it.platformType == KotlinPlatformType.common || it.platformType == KotlinPlatformType.native
}
}
internal fun dependsOnClosureWithInterCompilationDependencies(project: Project, sourceSet: KotlinSourceSet): Set<KotlinSourceSet> =
sourceSet.getSourceSetHierarchy().toMutableSet().apply {
/** exclude self from the results of [getSourceSetHierarchy] */
remove(sourceSet)
addAll(getVisibleSourceSetsFromAssociateCompilations(project, sourceSet))
}
/**
* @return All common source sets that can potentially be published. Right now, not all combinations of platforms actually
* support metadata compilation (see [KotlinMetadataTargetConfigurator.isMetadataCompilationSupported].
@@ -9,7 +9,7 @@ package org.jetbrains.kotlin.project.model
// TODO think about state management: unresolved -> (known dependency graph?) ... -> completely resolved
// it seems to be important to learn whether or not the model is final
interface ModuleDependencyResolver {
fun resolveDependency(moduleDependency: ModuleDependency): KotlinModule?
fun resolveDependency(moduleDependency: KotlinModuleDependency): KotlinModule?
}
// TODO merge with ModuleDependencyResolver?
@@ -17,5 +17,21 @@ interface ModuleDependencyResolver {
interface DependencyDiscovery {
// TODO return dependency graph rather than just iterable?
// TODO make this a partial function, too
fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency>
}
fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<KotlinModuleDependency>
}
interface KotlinDependencyGraphResolver {
// TODO add explicit dependency consistency scopes if we decide to keep non-production code in the same variant
fun resolveDependencyGraph(requestingModule: KotlinModule): DependencyGraphResolution
}
sealed class DependencyGraphResolution(val requestingModule: KotlinModule) {
class Unknown(requestingModule: KotlinModule) : DependencyGraphResolution(requestingModule)
class DependencyGraph(requestingModule: KotlinModule, val root: DependencyGraphNode): DependencyGraphResolution(requestingModule)
}
// TODO: should this be a single graph for all dependency scopes as well, not just for all fragments?
class DependencyGraphNode(
val module: KotlinModule,
val dependenciesByFragment: Map<KotlinModuleFragment, Iterable<DependencyGraphNode>>
)
@@ -1,135 +0,0 @@
/*
* Copyright 2010-2020 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.project.model
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
import java.util.ArrayDeque
interface InternalDependencyExpansion {
fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult
}
class InternalDependencyExpansionResult(
val entries: Iterable<Entry>
) {
sealed class ExpansionOutcome(val variantMatchingResults: Iterable<VariantResolution>) {
class VisibleFragments(
val fragments: Iterable<KotlinModuleFragment>,
variantMatchingResults: Iterable<VariantResolution>
) : ExpansionOutcome(
variantMatchingResults
)
class Failure(variantMatchingResults: Iterable<VariantResolution>) : ExpansionOutcome(variantMatchingResults)
}
class Entry(
val dependingFragment: KotlinModuleFragment,
val dependencyFragment: KotlinModuleFragment,
val outcome: ExpansionOutcome
)
}
fun InternalDependencyExpansionResult.visibleFragments(): List<KotlinModuleFragment> =
entries.flatMap { entry -> entry.outcome.visibleFragments() }
fun InternalDependencyExpansionResult.ExpansionOutcome.visibleFragments(): Iterable<KotlinModuleFragment> = when (this) {
is InternalDependencyExpansionResult.ExpansionOutcome.VisibleFragments -> fragments
else -> emptyList()
}
class DefaultInternalDependencyExpansion(
private val variantResolver: ContainingModuleVariantResolver
) : InternalDependencyExpansion {
interface ContainingModuleVariantResolver {
fun getChosenVariant(
dependingVariant: KotlinModuleVariant,
candidateVariants: Iterable<KotlinModuleVariant>
): VariantResolution
}
override fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult {
val visited = mutableSetOf(consumingFragment)
val answer = mutableListOf<InternalDependencyExpansionResult.Entry>()
/**
* For each fragment **t** that we consider a source of declared dependencies that we should expand for [consumingFragment]:
* - also consider the refines-closure of **t** as sources of declared dependencies
* - expand the declared dependencies of **t** to a set of fragments **F**
* - add the expansion result to the answer
* - also consider all fragments in **F** as sources of declared dependencies
*/
val declaredDependencySourceQueue = ArrayDeque<KotlinModuleFragment>().apply { add(consumingFragment) }
while (declaredDependencySourceQueue.isNotEmpty()) {
val declaredDependencySource = declaredDependencySourceQueue.removeFirst()
declaredDependencySourceQueue.addAll(declaredDependencySource.refinesClosure.filter(visited::add))
val declaredDependenciesToExpand = declaredDependencySource.declaredContainingModuleFragmentDependencies
declaredDependenciesToExpand.forEach { declaredDependency ->
val answerEntry = expandSingleDeclaredDependency(consumingFragment, declaredDependencySource, declaredDependency)
answer += answerEntry
declaredDependencySourceQueue.addAll(answerEntry.outcome.visibleFragments())
}
}
return InternalDependencyExpansionResult(answer)
}
private fun expandSingleDeclaredDependency(
consumingFragment: KotlinModuleFragment,
declaredDependencySource: KotlinModuleFragment,
declaredDependency: KotlinModuleFragment
): InternalDependencyExpansionResult.Entry {
/**
* Go over the variants containing the fragment that we infer the expansion for (note: it's the original consuming fragment, which
* may not necessarily be the same as the fragment holding the declared dependency if the latter comes from the closure).
*
* For each such containing variant v_i, let w_i be the variant containing the declared dependency fragment. Failure to choose
* prevents us from inferring the dependency expansion and is reported as a failure to expand the dependency.
*
* Then intersect the refined fragments of each w_i and use the intersection as the result.
*/
val (consumingVariants, producingVariants) =
listOf(consumingFragment, declaredDependency).map { it.containingModule.variantsContainingFragment(it).toSet() }
val chosenVariants = consumingVariants.map { consumingVariant ->
if (consumingVariant in producingVariants)
VariantResolution.VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant)
else
variantResolver.getChosenVariant(consumingVariant, producingVariants)
}
val mismatchedConsumingVariants = chosenVariants.filter { it !is VariantResolution.VariantMatch }
val outcome =
if (mismatchedConsumingVariants.isNotEmpty())
InternalDependencyExpansionResult.ExpansionOutcome.Failure(chosenVariants)
else
InternalDependencyExpansionResult.ExpansionOutcome.VisibleFragments(
chosenVariants
.map { (it as VariantResolution.VariantMatch).chosenVariant.refinesClosure }
.reduce { acc, it -> acc.intersect(it) },
chosenVariants
)
return InternalDependencyExpansionResult.Entry(declaredDependencySource, declaredDependency, outcome)
}
}
/**
* This implementation matches the variants of the module by checking
* their [KotlinModuleFragment.declaredContainingModuleFragmentDependencies], similar to how associate compilations work in MPP.
* If more than one such dependency is declared, the result is deemed ambiguous.
*/
class AssociateVariants : DefaultInternalDependencyExpansion.ContainingModuleVariantResolver {
override fun getChosenVariant(
dependingVariant: KotlinModuleVariant,
candidateVariants: Iterable<KotlinModuleVariant>
): VariantResolution {
val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies }
return VariantResolution.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result)
}
}
@@ -6,34 +6,47 @@
package org.jetbrains.kotlin.project.model
// TODO sealed with an abstract subclass? this will make exhaustive checks work
open class ModuleIdentifier
open class KotlinModuleIdentifier(open val moduleClassifier: String?)
// TODO consider id: Any, to allow IDs with custom equality?
data class LocalModuleIdentifier(val buildId: String, val projectId: String) : ModuleIdentifier() {
data class LocalModuleIdentifier(
val buildId: String,
val projectId: String,
override val moduleClassifier: String?
) : KotlinModuleIdentifier(moduleClassifier) {
companion object {
private const val SINGLE_BUILD_ID = ":"
}
override fun toString(): String = "project '$projectId'" + buildId.takeIf { it != SINGLE_BUILD_ID }?.let { "(build '$it')" }.orEmpty()
override fun toString(): String =
"project '$projectId'" +
moduleClassifier?.let { " / $it" }.orEmpty() +
buildId.takeIf { it != SINGLE_BUILD_ID }?.let { "(build '$it')" }.orEmpty()
}
data class MavenModuleIdentifier(val group: String, val name: String) : ModuleIdentifier() {
override fun toString(): String = "$group:$name"
data class MavenModuleIdentifier(
val group: String,
val name: String,
override val moduleClassifier: String?
) : KotlinModuleIdentifier(moduleClassifier) {
override fun toString(): String = "$group:$name" + moduleClassifier?.let { " / $it" }.orEmpty()
}
// TODO Gradle allows having multiple capabilities in a published module, we need to figure out how we can include them in the module IDs
interface KotlinModule {
val moduleIdentifier: ModuleIdentifier
val moduleIdentifier: KotlinModuleIdentifier
val fragments: Iterable<KotlinModuleFragment>
val variants: Iterable<KotlinModuleVariant>
get() = fragments.filterIsInstance<KotlinModuleVariant>()
// TODO: isSynthetic?
}
class BasicKotlinModule(
override val moduleIdentifier: ModuleIdentifier
override val moduleIdentifier: KotlinModuleIdentifier
) : KotlinModule {
override val fragments = mutableListOf<BasicKotlinModuleFragment>()
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.project.model
data class ModuleDependency(val moduleIdentifier: ModuleIdentifier) {
data class KotlinModuleDependency(val moduleIdentifier: KotlinModuleIdentifier) {
override fun toString(): String = "dependency $moduleIdentifier"
}
@@ -14,10 +14,8 @@ interface KotlinModuleFragment {
val fragmentName: String
val directRefinesDependencies: Iterable<KotlinModuleFragment>
val declaredContainingModuleFragmentDependencies: Iterable<KotlinModuleFragment>
// TODO: scopes
val declaredModuleDependencies: Iterable<ModuleDependency>
val declaredModuleDependencies: Iterable<KotlinModuleDependency>
val kotlinSourceRoots: Iterable<File>
}
@@ -54,8 +52,7 @@ open class BasicKotlinModuleFragment(
override val directRefinesDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val declaredContainingModuleFragmentDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val declaredModuleDependencies: MutableSet<ModuleDependency> = mutableSetOf()
override val declaredModuleDependencies: MutableSet<KotlinModuleDependency> = mutableSetOf()
override var kotlinSourceRoots: Iterable<File> = emptyList()
override fun toString(): String = "fragment $fragmentName"
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.project.model
import org.jetbrains.kotlin.project.model.utils.variantsContainingFragment
interface KotlinModuleFragmentsResolver {
interface ModuleFragmentsResolver {
fun getChosenFragments(
requestingFragment: KotlinModuleFragment,
dependencyModule: KotlinModule
@@ -30,18 +30,23 @@ sealed class FragmentResolution(val requestingFragment: KotlinModuleFragment, va
FragmentResolution(requestingFragment, dependencyModule)
}
class DefaultKotlinModuleFragmentsResolver(
class DefaultModuleFragmentsResolver(
private val variantResolver: ModuleVariantResolver
) : KotlinModuleFragmentsResolver {
) : ModuleFragmentsResolver {
override fun getChosenFragments(
requestingFragment: KotlinModuleFragment,
dependencyModule: KotlinModule
): FragmentResolution.ChosenFragments {
): FragmentResolution {
val dependingModule = requestingFragment.containingModule
val containingVariants = dependingModule.variantsContainingFragment(requestingFragment)
val chosenVariants = containingVariants.map { variantResolver.getChosenVariant(it, dependencyModule) }
// TODO: extend this to more cases with non-matching variants, revisit the behavior when no matching variant is found once we fix
// local publishing of libraries with missing host-specific parts (it breaks transitive dependencies now)
if (chosenVariants.none { it is VariantResolution.VariantMatch })
return FragmentResolution.NotRequested(requestingFragment, dependencyModule)
val chosenFragments = chosenVariants.map { variantResolution ->
when (variantResolution) {
is VariantResolution.VariantMatch -> variantResolution.chosenVariant.refinesClosure
@@ -9,11 +9,11 @@ import org.junit.Assume.assumeTrue
import org.junit.Test
import kotlin.test.assertEquals
class DefaultKotlinModuleFragmentsResolverTest {
class DefaultModuleFragmentsResolverTest {
val moduleFoo = simpleModule("foo")
val moduleBar = simpleModule("bar")
val fragmentResolver = DefaultKotlinModuleFragmentsResolver(MatchVariantsByExactAttributes())
val fragmentResolver = DefaultModuleFragmentsResolver(MatchVariantsByExactAttributes())
@Test
fun testFragmentVisibility() {