Dependency resolution API in PM2.0

This commit is contained in:
Sergey Igushkin
2020-11-16 19:05:25 +03:00
committed by TeamCityServer
parent f189ebc983
commit 994f940a31
10 changed files with 173 additions and 92 deletions
@@ -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<ModuleDependency>
}
@@ -15,15 +15,15 @@ interface InternalDependencyExpansion {
class InternalDependencyExpansionResult(
val entries: Iterable<Entry>
) {
sealed class ExpansionOutcome(val variantMatchingResults: Iterable<KotlinVariantMatchingResult>) {
sealed class ExpansionOutcome(val variantMatchingResults: Iterable<VariantResolution>) {
class VisibleFragments(
val fragments: Iterable<KotlinModuleFragment>,
variantMatchingResults: Iterable<KotlinVariantMatchingResult>
variantMatchingResults: Iterable<VariantResolution>
) : ExpansionOutcome(
variantMatchingResults
)
class Failure(variantMatchingResults: Iterable<KotlinVariantMatchingResult>) : ExpansionOutcome(variantMatchingResults)
class Failure(variantMatchingResults: Iterable<VariantResolution>) : ExpansionOutcome(variantMatchingResults)
}
class Entry(
@@ -48,7 +48,7 @@ class DefaultInternalDependencyExpansion(
fun getChosenVariant(
dependingVariant: KotlinModuleVariant,
candidateVariants: Iterable<KotlinModuleVariant>
): 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<KotlinModuleVariant>
): VariantResolution {
val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies }
return VariantResolution.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result)
}
}
@@ -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<String>) : ModuleOrigin()
interface KotlinModule {
val moduleName: String
val moduleSource: ModuleSource
val moduleOrigin: ModuleOrigin
val fragments: Iterable<KotlinModuleFragment>
@@ -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<BasicKotlinModuleFragment>()
@@ -16,6 +16,9 @@ interface KotlinModuleFragment {
val declaredContainingModuleFragmentDependencies: Iterable<KotlinModuleFragment>
// TODO: scopes
val declaredModuleDependencies: Iterable<ModuleDependency>
val kotlinSourceRoots: Iterable<File>
}
@@ -28,7 +31,7 @@ interface KotlinModuleVariant : KotlinModuleFragment {
val KotlinModuleFragment.fragmentAttributeSets: Map<KotlinAttributeKey, Set<String>>
get() = mutableMapOf<KotlinAttributeKey, MutableSet<String>>().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<BasicKotlinModuleFragment> = mutableListOf()
override val directRefinesDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val declaredContainingModuleFragmentDependencies: MutableList<BasicKotlinModuleFragment> = mutableListOf()
override val kotlinSourceRoots: Iterable<File> = emptyList()
override val declaredContainingModuleFragmentDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val declaredModuleDependencies: MutableSet<ModuleDependency> = mutableSetOf()
override var kotlinSourceRoots: Iterable<File> = emptyList()
override fun toString(): String = "fragment $fragmentName"
}
class BasicKotlinVariant(
class BasicKotlinModuleVariant(
containingModule: KotlinModule,
fragmentName: String
) : BasicKotlinModuleFragment (
@@ -14,11 +14,11 @@ interface KotlinModuleFragmentResolver {
class KotlinChosenFragments(
val module: KotlinModule,
val chosenFragments: Iterable<KotlinModuleFragment>,
val variantMatchingResults: Iterable<KotlinVariantMatchingResult>
val variantMatchingResults: Iterable<VariantResolution>
)
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()
}
}
@@ -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<KotlinModuleVariant>
) = 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<KotlinModuleVariant>
) : KotlinVariantMatchingResult(requestingVariant, dependencyModule)
@@ -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})"
}
@@ -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<KotlinModuleVariant>
) = 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<KotlinModuleVariant>
) : VariantResolution(requestingVariant, dependencyModule)
}
@@ -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<KotlinModuleVariant>
): KotlinVariantMatchingResult {
val result = candidateVariants.filter { it in dependingVariant.declaredContainingModuleFragmentDependencies }
return KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependingVariant.containingModule, result)
}.let { VariantResolution.fromMatchingVariants(requestingVariant, dependencyModule, it) }
}
}
@@ -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)