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( class InternalDependencyExpansionResult(
val entries: Iterable<Entry> val entries: Iterable<Entry>
) { ) {
sealed class ExpansionOutcome(val variantMatchingResults: Iterable<KotlinVariantMatchingResult>) { sealed class ExpansionOutcome(val variantMatchingResults: Iterable<VariantResolution>) {
class VisibleFragments( class VisibleFragments(
val fragments: Iterable<KotlinModuleFragment>, val fragments: Iterable<KotlinModuleFragment>,
variantMatchingResults: Iterable<KotlinVariantMatchingResult> variantMatchingResults: Iterable<VariantResolution>
) : ExpansionOutcome( ) : ExpansionOutcome(
variantMatchingResults variantMatchingResults
) )
class Failure(variantMatchingResults: Iterable<KotlinVariantMatchingResult>) : ExpansionOutcome(variantMatchingResults) class Failure(variantMatchingResults: Iterable<VariantResolution>) : ExpansionOutcome(variantMatchingResults)
} }
class Entry( class Entry(
@@ -48,7 +48,7 @@ class DefaultInternalDependencyExpansion(
fun getChosenVariant( fun getChosenVariant(
dependingVariant: KotlinModuleVariant, dependingVariant: KotlinModuleVariant,
candidateVariants: Iterable<KotlinModuleVariant> candidateVariants: Iterable<KotlinModuleVariant>
): KotlinVariantMatchingResult ): VariantResolution
} }
override fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult { override fun expandInternalFragmentDependencies(consumingFragment: KotlinModuleFragment): InternalDependencyExpansionResult {
@@ -97,22 +97,34 @@ class DefaultInternalDependencyExpansion(
val chosenVariants = consumingVariants.map { consumingVariant -> val chosenVariants = consumingVariants.map { consumingVariant ->
if (consumingVariant in producingVariants) if (consumingVariant in producingVariants)
VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant) VariantResolution.VariantMatch(consumingVariant, consumingVariant.containingModule, consumingVariant)
else else
variantResolver.getChosenVariant(consumingVariant, producingVariants) variantResolver.getChosenVariant(consumingVariant, producingVariants)
} }
val mismatchedConsumingVariants = chosenVariants.filter { it !is VariantMatch } val mismatchedConsumingVariants = chosenVariants.filter { it !is VariantResolution.VariantMatch }
val outcome = val outcome =
if (mismatchedConsumingVariants.isNotEmpty()) if (mismatchedConsumingVariants.isNotEmpty())
InternalDependencyExpansionResult.ExpansionOutcome.Failure(chosenVariants) InternalDependencyExpansionResult.ExpansionOutcome.Failure(chosenVariants)
else else
InternalDependencyExpansionResult.ExpansionOutcome.VisibleFragments( 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 chosenVariants
) )
return InternalDependencyExpansionResult.Entry(declaredDependencySource, declaredDependency, outcome) 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 package org.jetbrains.kotlin.project.model
sealed class ModuleSource { sealed class ModuleOrigin
class LocalBuild(val buildId: String) : ModuleSource() data class LocalBuild(val buildId: String) : ModuleOrigin() // TODO add project ID?
class ExternalDependency(val dependencyId: String) : ModuleSource() data class ExternalOrigin(val dependencyIdParts: List<String>) : ModuleOrigin()
}
interface KotlinModule { interface KotlinModule {
val moduleName: String val moduleName: String
val moduleSource: ModuleSource val moduleOrigin: ModuleOrigin
val fragments: Iterable<KotlinModuleFragment> val fragments: Iterable<KotlinModuleFragment>
@@ -22,7 +21,7 @@ interface KotlinModule {
class BasicKotlinModule( class BasicKotlinModule(
override val moduleName: String, override val moduleName: String,
override val moduleSource: ModuleSource override val moduleOrigin: ModuleOrigin
) : KotlinModule { ) : KotlinModule {
override val fragments = mutableListOf<BasicKotlinModuleFragment>() override val fragments = mutableListOf<BasicKotlinModuleFragment>()
@@ -16,6 +16,9 @@ interface KotlinModuleFragment {
val declaredContainingModuleFragmentDependencies: Iterable<KotlinModuleFragment> val declaredContainingModuleFragmentDependencies: Iterable<KotlinModuleFragment>
// TODO: scopes
val declaredModuleDependencies: Iterable<ModuleDependency>
val kotlinSourceRoots: Iterable<File> val kotlinSourceRoots: Iterable<File>
} }
@@ -28,7 +31,7 @@ interface KotlinModuleVariant : KotlinModuleFragment {
val KotlinModuleFragment.fragmentAttributeSets: Map<KotlinAttributeKey, Set<String>> val KotlinModuleFragment.fragmentAttributeSets: Map<KotlinAttributeKey, Set<String>>
get() = mutableMapOf<KotlinAttributeKey, MutableSet<String>>().apply { get() = mutableMapOf<KotlinAttributeKey, MutableSet<String>>().apply {
containingModule.variantsContainingFragment(this@fragmentAttributeSets).forEach { variant -> containingModule.variantsContainingFragment(this@fragmentAttributeSets).forEach { variant ->
variant.variantAttributes.forEach { attribute, value -> variant.variantAttributes.forEach { (attribute, value) ->
getOrPut(attribute) { mutableSetOf() }.add(value) getOrPut(attribute) { mutableSetOf() }.add(value)
} }
} }
@@ -49,14 +52,16 @@ open class BasicKotlinModuleFragment(
override val fragmentName: String override val fragmentName: String
) : KotlinModuleFragment { ) : KotlinModuleFragment {
override val directRefinesDependencies: MutableList<BasicKotlinModuleFragment> = mutableListOf() override val directRefinesDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val declaredContainingModuleFragmentDependencies: MutableList<BasicKotlinModuleFragment> = mutableListOf() override val declaredContainingModuleFragmentDependencies: MutableSet<BasicKotlinModuleFragment> = mutableSetOf()
override val kotlinSourceRoots: Iterable<File> = emptyList() override val declaredModuleDependencies: MutableSet<ModuleDependency> = mutableSetOf()
override var kotlinSourceRoots: Iterable<File> = emptyList()
override fun toString(): String = "fragment $fragmentName" override fun toString(): String = "fragment $fragmentName"
} }
class BasicKotlinVariant( class BasicKotlinModuleVariant(
containingModule: KotlinModule, containingModule: KotlinModule,
fragmentName: String fragmentName: String
) : BasicKotlinModuleFragment ( ) : BasicKotlinModuleFragment (
@@ -14,11 +14,11 @@ interface KotlinModuleFragmentResolver {
class KotlinChosenFragments( class KotlinChosenFragments(
val module: KotlinModule, val module: KotlinModule,
val chosenFragments: Iterable<KotlinModuleFragment>, val chosenFragments: Iterable<KotlinModuleFragment>,
val variantMatchingResults: Iterable<KotlinVariantMatchingResult> val variantMatchingResults: Iterable<VariantResolution>
) )
class DefaultKotlinModuleFragmentResolver( class DefaultKotlinModuleFragmentResolver(
private val variantResolver: KotlinModuleVariantResolver private val variantResolver: ModuleVariantResolver
) : KotlinModuleFragmentResolver { ) : KotlinModuleFragmentResolver {
override fun getChosenFragments(dependingFragment: KotlinModuleFragment, dependencyModule: KotlinModule): KotlinChosenFragments { override fun getChosenFragments(dependingFragment: KotlinModuleFragment, dependencyModule: KotlinModule): KotlinChosenFragments {
val dependingModule = dependingFragment.containingModule val dependingModule = dependingFragment.containingModule
@@ -28,7 +28,7 @@ class DefaultKotlinModuleFragmentResolver(
val chosenFragments = chosenVariants.map { variantResolution -> val chosenFragments = chosenVariants.map { variantResolution ->
when (variantResolution) { when (variantResolution) {
is VariantMatch -> variantResolution.chosenVariant.refinesClosure is VariantResolution.VariantMatch -> variantResolution.chosenVariant.refinesClosure
else -> emptySet() 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 package org.jetbrains.kotlin.project.model
class MatchVariantsByExactAttributes : KotlinModuleVariantResolver { class MatchVariantsByExactAttributes : ModuleVariantResolver {
override fun getChosenVariant(dependingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): KotlinVariantMatchingResult { override fun getChosenVariant(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): VariantResolution {
val candidates = dependencyModule.variants val candidates = dependencyModule.variants
return candidates.filter { candidate -> return candidates.filter { candidate ->
candidate.isExported && candidate.variantAttributes.all { (attributeKey, candidateValue) -> candidate.isExported && candidate.variantAttributes.all { (attributeKey, candidateValue) ->
attributeKey !in dependingVariant.variantAttributes.keys || attributeKey !in requestingVariant.variantAttributes.keys ||
candidateValue == dependingVariant.variantAttributes.getValue(attributeKey) candidateValue == requestingVariant.variantAttributes.getValue(attributeKey)
} }
}.let { KotlinVariantMatchingResult.fromMatchingVariants(dependingVariant, dependencyModule, it) } }.let { VariantResolution.fromMatchingVariants(requestingVariant, 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)
} }
} }
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.project.model 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 = fun BasicKotlinModule.fragment(vararg nameParts: String): BasicKotlinModuleFragment =
fragment(nameParts.drop(1).joinToString("", nameParts.first()) { it.capitalize() }) 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 = fun BasicKotlinModule.fragment(name: String): BasicKotlinModuleFragment =
fragments.firstOrNull { it.fragmentName == name } ?: BasicKotlinModuleFragment(this, name).also { fragments.add(it) } 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() }) 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 } fragments.firstOrNull { it.fragmentName == name }
?.let { it as? BasicKotlinVariant ?: error("$name is not a variant") } ?.let { it as? BasicKotlinModuleVariant ?: error("$name is not a variant") }
?: BasicKotlinVariant(this, name).also { fragments.add(it) } ?: BasicKotlinModuleVariant(this, name).also { fragments.add(it) }
fun BasicKotlinModuleFragment.depends(fragment: BasicKotlinModuleFragment) { 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) refines(common)
refinedBy(jvm) refinedBy(jvm)
refinedBy(js) refinedBy(js)
} }
val jsAndLinux = fragment("jsAndLinux", purpose).apply { fragment("jsAndLinux", purpose).apply {
refines(common) refines(common)
refinedBy(js) refinedBy(js)
refinedBy(linux) refinedBy(linux)