Resolve dependencies as graph, support multiple modules in a project
This commit is contained in:
committed by
TeamCityServer
parent
f1fb438d00
commit
d66f95b80e
+1
-2
@@ -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")
|
||||
|
||||
+157
-72
@@ -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)
|
||||
|
||||
|
||||
+78
-35
@@ -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()
|
||||
}
|
||||
|
||||
+15
-8
@@ -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()
|
||||
},
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+67
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
+114
-54
@@ -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
|
||||
|
||||
+5
-1
@@ -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 =
|
||||
|
||||
+19
-18
@@ -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>()
|
||||
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
+9
-4
@@ -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
|
||||
+2
-2
@@ -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() {
|
||||
Reference in New Issue
Block a user