diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt new file mode 100644 index 00000000000..3ac66198bd2 --- /dev/null +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/DependencyResolution.kt @@ -0,0 +1,16 @@ +/* + * 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 + +interface ModuleDependencyResolver { + fun resolveDependency(moduleDependency: ModuleDependency): KotlinModule? +} + +interface DependencyDiscovery { + // TODO return dependency graph rather than just iterable? + // TODO make this a partial function, too + fun discoverDependencies(fragment: KotlinModuleFragment): Iterable +} \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/InternalDependencyExpansion.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/InternalDependencyExpansion.kt index 4a6d856d1bb..9763af1614a 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/InternalDependencyExpansion.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/InternalDependencyExpansion.kt @@ -15,15 +15,15 @@ interface InternalDependencyExpansion { class InternalDependencyExpansionResult( val entries: Iterable ) { - sealed class ExpansionOutcome(val variantMatchingResults: Iterable) { + sealed class ExpansionOutcome(val variantMatchingResults: Iterable) { class VisibleFragments( val fragments: Iterable, - variantMatchingResults: Iterable + variantMatchingResults: Iterable ) : ExpansionOutcome( variantMatchingResults ) - class Failure(variantMatchingResults: Iterable) : ExpansionOutcome(variantMatchingResults) + class Failure(variantMatchingResults: Iterable) : ExpansionOutcome(variantMatchingResults) } class Entry( @@ -48,7 +48,7 @@ class DefaultInternalDependencyExpansion( fun getChosenVariant( dependingVariant: KotlinModuleVariant, candidateVariants: Iterable - ): KotlinVariantMatchingResult + ): VariantResolution } override fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult { @@ -97,22 +97,34 @@ class DefaultInternalDependencyExpansion( val chosenVariants = consumingVariants.map { consumingVariant -> if (consumingVariant in producingVariants) - VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant) + VariantResolution.VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant) else variantResolver.getChosenVariant(consumingVariant, producingVariants) } - val mismatchedConsumingVariants = chosenVariants.filter { it !is VariantMatch } + 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 VariantMatch).chosenVariant.refinesClosure }.reduce { acc, it -> acc.intersect(it) }, + chosenVariants + .map { (it as VariantResolution.VariantMatch).chosenVariant.refinesClosure } + .reduce { acc, it -> acc.intersect(it) }, chosenVariants ) return InternalDependencyExpansionResult.Entry(declaredDependencySource, declaredDependency, outcome) } +} + +class AssociateVariants : DefaultInternalDependencyExpansion.ContainingModuleVariantResolver { + override fun getChosenVariant( + dependingVariant: KotlinModuleVariant, + candidateVariants: Iterable + ): VariantResolution { + val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies } + return VariantResolution.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result) + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt index 8a75f2b6897..f1586a6df7a 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModule.kt @@ -5,14 +5,13 @@ package org.jetbrains.kotlin.project.model -sealed class ModuleSource { - class LocalBuild(val buildId: String) : ModuleSource() - class ExternalDependency(val dependencyId: String) : ModuleSource() -} +sealed class ModuleOrigin +data class LocalBuild(val buildId: String) : ModuleOrigin() // TODO add project ID? +data class ExternalOrigin(val dependencyIdParts: List) : ModuleOrigin() interface KotlinModule { val moduleName: String - val moduleSource: ModuleSource + val moduleOrigin: ModuleOrigin val fragments: Iterable @@ -22,7 +21,7 @@ interface KotlinModule { class BasicKotlinModule( override val moduleName: String, - override val moduleSource: ModuleSource + override val moduleOrigin: ModuleOrigin ) : KotlinModule { override val fragments = mutableListOf() diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragment.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragment.kt index 62234d670fc..dbfa7d7c9d4 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragment.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragment.kt @@ -16,6 +16,9 @@ interface KotlinModuleFragment { val declaredContainingModuleFragmentDependencies: Iterable + // TODO: scopes + val declaredModuleDependencies: Iterable + val kotlinSourceRoots: Iterable } @@ -28,7 +31,7 @@ interface KotlinModuleVariant : KotlinModuleFragment { val KotlinModuleFragment.fragmentAttributeSets: Map> get() = mutableMapOf>().apply { containingModule.variantsContainingFragment(this@fragmentAttributeSets).forEach { variant -> - variant.variantAttributes.forEach { attribute, value -> + variant.variantAttributes.forEach { (attribute, value) -> getOrPut(attribute) { mutableSetOf() }.add(value) } } @@ -49,14 +52,16 @@ open class BasicKotlinModuleFragment( override val fragmentName: String ) : KotlinModuleFragment { - override val directRefinesDependencies: MutableList = mutableListOf() + override val directRefinesDependencies: MutableSet = mutableSetOf() - override val declaredContainingModuleFragmentDependencies: MutableList = mutableListOf() - override val kotlinSourceRoots: Iterable = emptyList() + override val declaredContainingModuleFragmentDependencies: MutableSet = mutableSetOf() + override val declaredModuleDependencies: MutableSet = mutableSetOf() + + override var kotlinSourceRoots: Iterable = emptyList() override fun toString(): String = "fragment $fragmentName" } -class BasicKotlinVariant( +class BasicKotlinModuleVariant( containingModule: KotlinModule, fragmentName: String ) : BasicKotlinModuleFragment ( diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragmentResolver.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragmentResolver.kt index 344b95d981c..38495879cc3 100644 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragmentResolver.kt +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleFragmentResolver.kt @@ -14,11 +14,11 @@ interface KotlinModuleFragmentResolver { class KotlinChosenFragments( val module: KotlinModule, val chosenFragments: Iterable, - val variantMatchingResults: Iterable + val variantMatchingResults: Iterable ) class DefaultKotlinModuleFragmentResolver( - private val variantResolver: KotlinModuleVariantResolver + private val variantResolver: ModuleVariantResolver ) : KotlinModuleFragmentResolver { override fun getChosenFragments(dependingFragment: KotlinModuleFragment, dependencyModule: KotlinModule): KotlinChosenFragments { val dependingModule = dependingFragment.containingModule @@ -28,7 +28,7 @@ class DefaultKotlinModuleFragmentResolver( val chosenFragments = chosenVariants.map { variantResolution -> when (variantResolution) { - is VariantMatch -> variantResolution.chosenVariant.refinesClosure + is VariantResolution.VariantMatch -> variantResolution.chosenVariant.refinesClosure else -> emptySet() } } diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleVariantResolver.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleVariantResolver.kt deleted file mode 100644 index f4d149028e4..00000000000 --- a/libraries/tools/kotlin-project-model/src/main/kotlin/KotlinModuleVariantResolver.kt +++ /dev/null @@ -1,49 +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 - -/* - * 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. - */ - -interface KotlinModuleVariantResolver { - fun getChosenVariant(dependingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): KotlinVariantMatchingResult -} - -sealed class KotlinVariantMatchingResult( - val requestingVariant: KotlinModuleVariant, - val dependencyModule: KotlinModule -) { - companion object { - fun fromMatchingVariants( - requestingVariant: KotlinModuleVariant, - dependencyModule: KotlinModule, - matchingVariants: Collection - ) = when (matchingVariants.size) { - 0 -> NoVariantMatch(requestingVariant, dependencyModule) - 1 -> VariantMatch(requestingVariant, dependencyModule, matchingVariants.single()) - else -> AmbiguousVariants(requestingVariant, dependencyModule, matchingVariants) - } - } -} - -class VariantMatch( - requestingVariant: KotlinModuleVariant, - dependencyModule: KotlinModule, - val chosenVariant: KotlinModuleVariant -) : KotlinVariantMatchingResult(requestingVariant, dependencyModule) - -class NoVariantMatch( - requestingVariant: KotlinModuleVariant, - dependencyModule: KotlinModule -) : KotlinVariantMatchingResult(requestingVariant, dependencyModule) - -class AmbiguousVariants( - requestingVariant: KotlinModuleVariant, - dependencyModule: KotlinModule, - val matchingVariants: Iterable -) : KotlinVariantMatchingResult(requestingVariant, dependencyModule) diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleDependency.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleDependency.kt new file mode 100644 index 00000000000..25a07ab9571 --- /dev/null +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleDependency.kt @@ -0,0 +1,17 @@ +/* + * 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 + +sealed class ModuleDependency(open val moduleOrigin: ModuleOrigin) + +data class ExternalModuleDependency(override val moduleOrigin: ExternalOrigin) : ModuleDependency(moduleOrigin) { + override fun toString() = "external dependency ${moduleOrigin.dependencyIdParts.joinToString(":")}" +} + +data class LocalModuleDependency(override val moduleOrigin: LocalBuild, val moduleName: String) : + ModuleDependency(moduleOrigin) { + override fun toString() = "local module $moduleName (build ${moduleOrigin.buildId})" +} \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleVariantResolver.kt b/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleVariantResolver.kt new file mode 100644 index 00000000000..1ff83e1a02d --- /dev/null +++ b/libraries/tools/kotlin-project-model/src/main/kotlin/ModuleVariantResolver.kt @@ -0,0 +1,91 @@ +/* + * 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 + +/* + * 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. + */ + +interface ModuleVariantResolver { + /** + * Find the matching module variant of the [dependencyModule] for the consumer's [requestingVariant]. + * + * This is a partial function. A particular resolver may not be capable to do variant matching for some [dependencyModule] or + * some [requestingVariant] (to such an extent that a resolver may only know how to resolve a single [dependencyModule]'s variants, or + * how to resolve module variants for a particular [requestingVariant]). + * In this case it should return [VariantResolution.Unknown], and the caller (which might be an aggregating [ModuleVariantResolver]) + * may consult other sources to get the variant match. + */ + fun getChosenVariant(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): VariantResolution +} + +/** + * Represents the results of [dependencyModule]'s variant resolution for the [requestingVariant], + * usually done by some [ModuleVariantResolver]. + */ +sealed class VariantResolution( + val requestingVariant: KotlinModuleVariant, + val dependencyModule: KotlinModule +) { + companion object { + fun fromMatchingVariants( + requestingVariant: KotlinModuleVariant, + dependencyModule: KotlinModule, + matchingVariants: Collection + ) = when (matchingVariants.size) { + 0 -> NoVariantMatch(requestingVariant, dependencyModule) + 1 -> VariantMatch(requestingVariant, dependencyModule, matchingVariants.single()) + else -> AmbiguousVariants(requestingVariant, dependencyModule, matchingVariants) + } + } + + override fun toString(): String = when (this) { + is VariantMatch -> "match: ${chosenVariant.fragmentName}" + is Unknown -> "unknown" + is NotRequested -> "not requested" + is NoVariantMatch -> "no match" + is AmbiguousVariants -> "ambiguity: ${matchingVariants.joinToString { it.fragmentName }}" + } + + /** + * The resolver decided that the [chosenVariant] is the best variant match. + */ + class VariantMatch( + requestingVariant: KotlinModuleVariant, + dependencyModule: KotlinModule, + val chosenVariant: KotlinModuleVariant + ) : VariantResolution(requestingVariant, dependencyModule) + + class Unknown(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule) : + VariantResolution(requestingVariant, dependencyModule) + + /** + * Returned when the resolver detects that the [requestingVariant] does not depend on [dependencyModule] and therefore should not get + * any variant of that module at all. + */ + class NotRequested(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule) : + VariantResolution(requestingVariant, dependencyModule) + + /** + * Returned when the resolver could not find any matching of the [dependencyModule] for the [requestingVariant], or variant matching was + * done externally and the external system did not provide any details of the failure. + */ + class NoVariantMatch( + requestingVariant: KotlinModuleVariant, + dependencyModule: KotlinModule + ) : VariantResolution(requestingVariant, dependencyModule) + + /** + * Returned when the resolver found multiple matching variants in the [dependencyModule] and failed to choose one of them as the + * best match for the [requestingVariant]. + */ + class AmbiguousVariants( + requestingVariant: KotlinModuleVariant, + dependencyModule: KotlinModule, + val matchingVariants: Iterable + ) : VariantResolution(requestingVariant, dependencyModule) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/DummyVariantMatchingImplementations.kt b/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/DummyVariantMatchingImplementations.kt index 94be14b6e0e..7ca19b93c47 100644 --- a/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/DummyVariantMatchingImplementations.kt +++ b/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/DummyVariantMatchingImplementations.kt @@ -5,24 +5,14 @@ package org.jetbrains.kotlin.project.model -class MatchVariantsByExactAttributes : KotlinModuleVariantResolver { - override fun getChosenVariant(dependingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): KotlinVariantMatchingResult { +class MatchVariantsByExactAttributes : ModuleVariantResolver { + override fun getChosenVariant(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): VariantResolution { val candidates = dependencyModule.variants return candidates.filter { candidate -> candidate.isExported && candidate.variantAttributes.all { (attributeKey, candidateValue) -> - attributeKey !in dependingVariant.variantAttributes.keys || - candidateValue == dependingVariant.variantAttributes.getValue(attributeKey) + attributeKey !in requestingVariant.variantAttributes.keys || + candidateValue == requestingVariant.variantAttributes.getValue(attributeKey) } - }.let { KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependencyModule, it) } - } -} - -class AssociateVariants : DefaultInternalDependencyExpansion.ContainingModuleVariantResolver { - override fun getChosenVariant( - dependingVariant: KotlinModuleVariant, - candidateVariants: Iterable - ): KotlinVariantMatchingResult { - val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies } - return KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result) + }.let { VariantResolution.fromMatchingVariants(requestingVariant, dependencyModule, it) } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/SimpleModel.kt b/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/SimpleModel.kt index bc775be42a1..0fc4cbc602e 100644 --- a/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/SimpleModel.kt +++ b/libraries/tools/kotlin-project-model/src/test/kotlin/org/jetbrains/kotlin/project/model/SimpleModel.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.project.model -fun module(name: String) = BasicKotlinModule(name, ModuleSource.LocalBuild("current")) +fun module(name: String) = BasicKotlinModule(name, LocalBuild("current")) fun BasicKotlinModule.fragment(vararg nameParts: String): BasicKotlinModuleFragment = fragment(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() }) @@ -13,13 +13,13 @@ fun BasicKotlinModule.fragment(vararg nameParts: String): BasicKotlinModuleFragm fun BasicKotlinModule.fragment(name: String): BasicKotlinModuleFragment = fragments.firstOrNull { it.fragmentName == name } ?: BasicKotlinModuleFragment(this, name).also { fragments.add(it) } -fun BasicKotlinModule.variant(vararg nameParts: String): BasicKotlinVariant = +fun BasicKotlinModule.variant(vararg nameParts: String): BasicKotlinModuleVariant = variant(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() }) -fun BasicKotlinModule.variant(name: String): BasicKotlinVariant = +fun BasicKotlinModule.variant(name: String): BasicKotlinModuleVariant = fragments.firstOrNull { it.fragmentName == name } - ?.let { it as? BasicKotlinVariant ?: error("$name is not a variant") } - ?: BasicKotlinVariant(this, name).also { fragments.add(it) } + ?.let { it as? BasicKotlinModuleVariant ?: error("$name is not a variant") } + ?: BasicKotlinModuleVariant(this, name).also { fragments.add(it) } fun BasicKotlinModuleFragment.depends(fragment: BasicKotlinModuleFragment) { @@ -61,12 +61,12 @@ fun simpleModule(name: String) = module(name).apply { } } - val jvmAndJs = fragment("jvmAndJs", purpose).apply { + fragment("jvmAndJs", purpose).apply { refines(common) refinedBy(jvm) refinedBy(js) } - val jsAndLinux = fragment("jsAndLinux", purpose).apply { + fragment("jsAndLinux", purpose).apply { refines(common) refinedBy(js) refinedBy(linux)