Handle dependency module IDs more carefully in dependency resolution

This commit is contained in:
Sergey Igushkin
2021-03-19 18:21:28 +03:00
parent ea458e6848
commit 401fbae8d8
5 changed files with 147 additions and 68 deletions
@@ -14,16 +14,17 @@ import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.pm20Extension
import org.jetbrains.kotlin.gradle.dsl.topLevelExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toModuleIdentifier
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toSingleModuleIdentifier
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 org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import java.io.File
import java.io.InputStream
import java.util.*
@@ -357,7 +358,7 @@ internal fun requestedDependencies(
}
internal abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) {
internal abstract class MppDependencyMetadataExtractor(val project: Project, val component: ResolvedComponentResult) {
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
abstract fun getExtractableMetadataFiles(
@@ -369,10 +370,18 @@ internal abstract class MppDependencyMetadataExtractor(val project: Project, val
private class ProjectMppDependencyMetadataExtractor(
project: Project,
dependency: ResolvedComponentResult,
val moduleIdentifier: KotlinModuleIdentifier,
val dependencyProject: Project
) : MppDependencyMetadataExtractor(project, dependency) {
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? =
buildKotlinProjectStructureMetadata(dependencyProject)
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? {
val topLevelExtension = dependencyProject.topLevelExtension
return when {
topLevelExtension is KotlinPm20ProjectExtension -> buildProjectStructureMetadata(
topLevelExtension.modules.single { it.moduleIdentifier == moduleIdentifier }
)
else -> buildKotlinProjectStructureMetadata(dependencyProject)
}
}
override fun getExtractableMetadataFiles(
visibleSourceSetNames: Set<String>,
@@ -382,7 +391,8 @@ private class ProjectMppDependencyMetadataExtractor(
is KotlinMultiplatformExtension -> projectExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
.filter { it.name in visibleSourceSetNames }.associate { it.defaultSourceSet.name to it.output.classesDirs }
is KotlinPm20ProjectExtension -> {
val moduleId = dependency.toModuleIdentifier()
require(moduleIdentifier != null)
val moduleId = moduleIdentifier
val module = projectExtension.modules.single { it.moduleIdentifier == moduleId }
val metadataCompilationRegistry = projectExtension.metadataCompilationRegistryByModuleId.getValue(moduleId)
visibleSourceSetNames.associateWith {
@@ -447,7 +457,7 @@ private open class JarArtifactMppDependencyMetadataExtractor(
baseDir: File
): ExtractableMetadataFiles {
val primaryArtifact = primaryArtifact
val moduleId = ModuleIds.fromComponent(project, dependency)
val moduleId = ModuleIds.fromComponent(project, component)
return JarExtractableMetadataFiles(
moduleId,
@@ -531,9 +541,44 @@ internal fun getMetadataExtractor(
resolveViaAvailableAt: Boolean
): MppDependencyMetadataExtractor? {
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
val moduleIdentifier = ModuleIds.fromComponent(project, resolvedComponentResult)
val moduleIdentifier = resolvedComponentResult.toSingleModuleIdentifier() // FIXME this loses information about auxiliary module deps
// TODO check how this code works with multi-capability resolutions
return mppDependencyMetadataExtractor(
resolvedMppVariantsProvider,
moduleIdentifier,
configuration,
resolveViaAvailableAt,
resolvedComponentResult,
project
)
}
internal fun getMetadataExtractor(
project: Project,
resolvedComponentResult: ResolvedComponentResult,
moduleIdentifier: KotlinModuleIdentifier,
configuration: Configuration
): MppDependencyMetadataExtractor? {
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
return mppDependencyMetadataExtractor(
resolvedMppVariantsProvider,
moduleIdentifier,
configuration,
true,
resolvedComponentResult,
project
)
}
private fun mppDependencyMetadataExtractor(
resolvedMppVariantsProvider: ResolvedMppVariantsProvider,
moduleIdentifier: KotlinModuleIdentifier,
configuration: Configuration,
resolveViaAvailableAt: Boolean,
resolvedComponentResult: ResolvedComponentResult,
project: Project
): MppDependencyMetadataExtractor? {
var resolvedViaAvailableAt = false
val metadataArtifact = resolvedMppVariantsProvider.getResolvedArtifactByPlatformModule(
@@ -570,9 +615,14 @@ internal fun getMetadataExtractor(
internal fun getProjectStructureMetadata(
project: Project,
module: ResolvedComponentResult,
configuration: Configuration
configuration: Configuration,
moduleIdentifier: KotlinModuleIdentifier? = null
): KotlinProjectStructureMetadata? {
val extractor = getMetadataExtractor(project, module, configuration, resolveViaAvailableAt = true)
val extractor = if (moduleIdentifier != null)
getMetadataExtractor(project, module, moduleIdentifier, configuration)
else
getMetadataExtractor(project, module, configuration, resolveViaAvailableAt = true)
return extractor?.getProjectStructureMetadata()
}
@@ -14,7 +14,10 @@ import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.artifacts.result.ResolvedVariantResult
import org.gradle.api.attributes.Usage
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toModuleIdentifiers
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toSingleModuleIdentifier
import org.jetbrains.kotlin.gradle.plugin.usageByName
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import java.io.File
internal class ResolvedMppVariantsProvider private constructor(private val project: Project) {
@@ -32,7 +35,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
/** Gets the name of the variant that the module specified by the [moduleIdentifier] resolved to in the given [configuration].
* The [moduleIdentifier] may be either the root module or a platform-specific module, the result is the same for the two cases. */
fun getResolvedVariantName(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): String? =
fun getResolvedVariantName(moduleIdentifier: KotlinModuleIdentifier, configuration: Configuration): String? =
getEntryForModule(moduleIdentifier).run {
if (configuration !in resolvedVariantsByConfiguration) {
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NONE)
@@ -45,7 +48,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
* module of a multiplatform project, not one of its platform-specific modules, as seen in the given [configuration].
* If the [configuration] requests platform artifacts and not the common code metadata, then this function will resolve its
* dependencies to metadata separately. */
fun getHostSpecificMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
fun getHostSpecificMetadataArtifactByRootModule(rootModuleIdentifier: KotlinModuleIdentifier, configuration: Configuration): File? {
val rootModuleEntry = getEntryForModule(rootModuleIdentifier)
val platformModuleEntry = rootModuleEntry.run {
@@ -67,7 +70,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
}
/** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */
fun getResolvedArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
fun getResolvedArtifactByPlatformModule(moduleIdentifier: KotlinModuleIdentifier, configuration: Configuration): File? =
getEntryForModule(moduleIdentifier).run {
if (configuration !in resolvedArtifactByConfiguration) {
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
@@ -76,10 +79,10 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
resolvedArtifactByConfiguration.getOrPut(configuration) { null }
}
private fun getEntryForModule(moduleIdentifier: ModuleDependencyIdentifier) =
private fun getEntryForModule(moduleIdentifier: KotlinModuleIdentifier) =
entriesCache.getOrPut(moduleIdentifier, { ModuleEntry(moduleIdentifier) })
private val entriesCache: MutableMap<ModuleDependencyIdentifier, ModuleEntry> = mutableMapOf()
private val entriesCache: MutableMap<KotlinModuleIdentifier, ModuleEntry> = mutableMapOf()
private val mppComponentsByConfiguration: MutableMap<Configuration, Set<ResolvedComponentResult>> = mutableMapOf()
@@ -105,22 +108,25 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
val result = mutableListOf<ResolvedComponentResult>()
configuration.incoming.resolutionResult.allComponents { component ->
val moduleId = ModuleIds.fromComponent(project, component)
val variants = component.variants
val isMpp = variants.any { variant -> variant.attributes.keySet().any { it.name == KotlinPlatformType.attribute.name } }
val isMpp =
component.dependents.isNotEmpty() && // filter out the root of the dependency graph, we are not interested in it
component.variants.any { variant -> variant.attributes.keySet().any { it.name == KotlinPlatformType.attribute.name } }
if (isMpp) {
result.add(component)
val moduleEntry = getEntryForModule(moduleId)
moduleEntry.resolvedVariantsByConfiguration[configuration] = variants
component.dependents.forEach { dependent ->
dependent.requested.toModuleIdentifiers().forEach { moduleId ->
val moduleEntry = getEntryForModule(moduleId)
moduleEntry.resolvedVariantsByConfiguration[configuration] = listOf(dependent.resolvedVariant)
moduleEntry.dependenciesByConfiguration[configuration] = component.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { dependency -> ModuleIds.fromComponent(project, dependency.selected) }
moduleEntry.dependenciesByConfiguration[configuration] = component.dependencies
.filterIsInstance<ResolvedDependencyResult>()
.map { dependency -> dependency.selected.toSingleModuleIdentifier() }
if (component.id is ProjectComponentIdentifier) {
// Then the platform variant chosen for this module is definitely inside the module itself:
moduleEntry.chosenPlatformModuleByConfiguration[configuration] = moduleEntry
if (component.id is ProjectComponentIdentifier) {
// Then the platform variant chosen for this module is definitely inside the module itself:
moduleEntry.chosenPlatformModuleByConfiguration[configuration] = moduleEntry
}
}
}
}
}
@@ -160,36 +166,40 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
configuration: Configuration,
artifactResolutionMode: ArtifactResolutionMode
) {
val mppModuleIds = mppComponentIds.mapTo(mutableSetOf()) { ModuleIds.fromComponent(project, it) }
val mppModuleIds = mppComponentIds.flatMapTo(mutableSetOf()) { componentId ->
componentId.toModuleIdentifiers()
}
mppComponentIds.forEach { componentId ->
val moduleEntry = getEntryForModule(ModuleIds.fromComponent(project, componentId))
val artifact = artifacts[componentId]
when {
// With project dependencies, we don't need the host-specific metadata artifacts, as we have the compilation outputs:
componentId is ProjectComponentIdentifier -> {
moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null
}
// We found a requested artifact of the MPP; it is one of: platform artifact, root metadata, host-specific metadata
artifact != null -> {
val resolvedArtifactMap = when (artifactResolutionMode) {
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
ArtifactResolutionMode.METADATA -> moduleEntry.resolvedMetadataArtifactByConfiguration
else -> error("unexpected $artifactResolutionMode")
componentId.toModuleIdentifiers().forEach { moduleId ->
val moduleEntry = getEntryForModule(moduleId)
val artifact = artifacts[componentId]
when {
// With project dependencies, we don't need the host-specific metadata artifacts, as we have the compilation outputs:
componentId is ProjectComponentIdentifier -> {
moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null
}
resolvedArtifactMap[configuration] = artifact.file
}
// Otherwise, this may be a root module of some MPP that resolved to a variant in another module. Take a note of that.
else -> {
// TODO: there's an assumption that resolving a root MPP module to a host-specific metadata artifact and to a platform
// artifact will choose variants that are published within the same Maven module; change this code if that's not
// true anymore.
val singleDependencyId = moduleEntry.dependenciesByConfiguration.getValue(configuration).singleOrNull()
if (singleDependencyId != null && singleDependencyId in mppModuleIds) {
moduleEntry.chosenPlatformModuleByConfiguration[configuration] =
getEntryForModule(singleDependencyId)
// We found a requested artifact of the MPP; it is one of: platform artifact, root metadata, host-specific metadata
artifact != null -> {
val resolvedArtifactMap = when (artifactResolutionMode) {
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
ArtifactResolutionMode.METADATA -> moduleEntry.resolvedMetadataArtifactByConfiguration
else -> error("unexpected $artifactResolutionMode")
}
resolvedArtifactMap[configuration] = artifact.file
}
// Otherwise, this may be a root module of some MPP that resolved to a variant in another module. Take a note of that.
else -> {
// TODO: there's an assumption that resolving a root MPP module to a host-specific metadata artifact and to a platform
// artifact will choose variants that are published within the same Maven module; change this code if that's not
// true anymore.
val singleDependencyId = moduleEntry.dependenciesByConfiguration.getValue(configuration).singleOrNull()
if (singleDependencyId != null && singleDependencyId in mppModuleIds) {
moduleEntry.chosenPlatformModuleByConfiguration[configuration] =
getEntryForModule(singleDependencyId)
}
}
}
}
@@ -203,9 +213,9 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
*/
private class ModuleEntry(
@Suppress("unused") // simplify debugging
val moduleIdentifier: ModuleDependencyIdentifier
val moduleIdentifier: KotlinModuleIdentifier
) {
val dependenciesByConfiguration: MutableMap<Configuration, List<ModuleDependencyIdentifier>> = HashMap()
val dependenciesByConfiguration: MutableMap<Configuration, List<KotlinModuleIdentifier>> = HashMap()
val resolvedVariantsByConfiguration: MutableMap<Configuration, List<ResolvedVariantResult?>?> = HashMap()
val resolvedArtifactByConfiguration: MutableMap<Configuration, File?> = HashMap()
val resolvedMetadataArtifactByConfiguration: MutableMap<Configuration, File?> = HashMap()
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toSingleModuleIdentifier
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import java.io.File
@@ -55,7 +56,8 @@ internal class SourceSetVisibilityProvider(
): SourceSetVisibilityResult {
val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom)
val mppModuleIdentifier = ModuleIds.fromComponent(project, resolvedRootMppDependency ?: resolvedMetadataDependency)
val component = resolvedRootMppDependency ?: resolvedMetadataDependency
val mppModuleIdentifier = component.toSingleModuleIdentifier()
val firstConfigurationByVariant = mutableMapOf<String, Configuration>()
@@ -43,7 +43,12 @@ class GradleModuleDependencyResolver(
projectModuleBuilder.buildModulesFromProject(project.project(id.projectPath))
.find { it.moduleIdentifier.moduleClassifier == classifier }
id is ModuleComponentIdentifier -> {
val metadata = getProjectStructureMetadata(project, component, configurationToResolve(requestingModule)) ?: return null
val metadata = getProjectStructureMetadata(
project,
component,
configurationToResolve(requestingModule),
moduleDependency.moduleIdentifier
) ?: return null
val result = projectStructureMetadataModuleBuilder.getModule(component, metadata)
result
}
@@ -195,7 +200,7 @@ class VariantDependencyDiscovery(
classifiers.map { LocalModuleIdentifier(id.build.name, id.projectPath, it) }
}
is ModuleComponentIdentifier -> {
classifiers.map { id.toModuleIdentifier(it) }
classifiers.map { id.toSingleModuleIdentifier(it) }
}
else -> return@flatMap emptyList<KotlinModuleIdentifier>() // TODO check that no other options are possible, throw errors?
}
@@ -203,18 +208,30 @@ class VariantDependencyDiscovery(
}
}
private fun ModuleComponentIdentifier.toModuleIdentifier(classifier: String? = null): MavenModuleIdentifier =
private fun ModuleComponentIdentifier.toSingleModuleIdentifier(classifier: String? = null): MavenModuleIdentifier =
MavenModuleIdentifier(moduleIdentifier.group, moduleIdentifier.name, classifier)
internal fun ComponentIdentifier.matchesModule(module: KotlinModule): Boolean =
matchesModuleIdentifier(module.moduleIdentifier)
internal fun ResolvedComponentResult.toModuleIdentifier(): KotlinModuleIdentifier {
val capabilities = variants.flatMap { it.capabilities } // expected to be disjoint
val moduleClassifier = moduleClassifiersFromCapabilities(capabilities).single() // FIXME handle multiple capabilities
internal fun ResolvedComponentResult.toModuleIdentifiers(): List<KotlinModuleIdentifier> {
val classifiers = moduleClassifiersFromCapabilities(variants.flatMap { it.capabilities })
return classifiers.map { moduleClassifier ->
when (val id = id) {
is ProjectComponentIdentifier -> LocalModuleIdentifier(id.build.name, id.projectPath, moduleClassifier)
is ModuleComponentIdentifier -> id.toSingleModuleIdentifier()
else -> MavenModuleIdentifier(moduleVersion?.group.orEmpty(), moduleVersion?.name.orEmpty(), moduleClassifier)
}
}
}
// FIXME this mapping doesn't have enough information to choose auxiliary modules
internal fun ResolvedComponentResult.toSingleModuleIdentifier(): KotlinModuleIdentifier {
val classifiers = moduleClassifiersFromCapabilities(variants.flatMap { it.capabilities })
val moduleClassifier = classifiers.single() // FIXME handle multiple capabilities
return when (val id = id) {
is ProjectComponentIdentifier -> LocalModuleIdentifier(id.build.name, id.projectPath, moduleClassifier)
is ModuleComponentIdentifier -> id.toModuleIdentifier()
is ModuleComponentIdentifier -> id.toSingleModuleIdentifier()
else -> MavenModuleIdentifier(moduleVersion?.group.orEmpty(), moduleVersion?.name.orEmpty(), moduleClassifier)
}
}
@@ -233,7 +250,7 @@ internal fun ComponentSelector.toModuleIdentifiers(): Iterable<KotlinModuleIdent
}
}
internal fun ResolvedComponentResult.toModuleDependency(): KotlinModuleDependency = KotlinModuleDependency(toModuleIdentifier())
internal fun ResolvedComponentResult.toModuleDependency(): KotlinModuleDependency = KotlinModuleDependency(toSingleModuleIdentifier())
internal fun ComponentSelector.toModuleDependency(): KotlinModuleDependency {
val moduleId = toModuleIdentifiers().single() // FIXME handle multiple
return KotlinModuleDependency(moduleId)
@@ -250,7 +267,7 @@ internal fun ComponentIdentifier.matchesModuleIdentifier(id: KotlinModuleIdentif
}
is MavenModuleIdentifier -> {
val componentId = this as? ModuleComponentIdentifier
componentId?.toModuleIdentifier() == id
componentId?.toSingleModuleIdentifier() == id
}
else -> false
}
@@ -28,7 +28,7 @@ internal class FragmentGranularMetadataResolver(
get() = requestingFragment.containingModule.project
private val parentResultsByModuleIdentifier: Map<KotlinModuleIdentifier, List<MetadataDependencyResolution>> by lazy {
refinesParentResolvers.value.flatMap { it.resolutions }.groupBy { it.dependency.toModuleIdentifier() }
refinesParentResolvers.value.flatMap { it.resolutions }.groupBy { it.dependency.toSingleModuleIdentifier() }
}
private val metadataModuleBuilder = ProjectStructureMetadataModuleBuilder()
@@ -41,7 +41,7 @@ internal class FragmentGranularMetadataResolver(
private fun doResolveMetadataDependencies(): Iterable<MetadataDependencyResolution> {
val configurationToResolve = configurationToResolveMetadataDependencies(project, requestingFragment.containingModule)
val resolvedComponentsByModuleId =
configurationToResolve.incoming.resolutionResult.allComponents.associateBy { it.toModuleIdentifier() }
configurationToResolve.incoming.resolutionResult.allComponents.associateBy { it.toSingleModuleIdentifier() }
val resolvedDependenciesByModuleId =
configurationToResolve.incoming.resolutionResult.allDependencies.filterIsInstance<ResolvedDependencyResult>()
.flatMap { dependency -> dependency.requested.toModuleIdentifiers().map { id -> id to dependency } }.toMap()
@@ -100,7 +100,7 @@ internal class FragmentGranularMetadataResolver(
}
val parentResolutionsForDependency =
parentResultsByModuleIdentifier[metadataSourceComponent.toModuleIdentifier()].orEmpty()
parentResultsByModuleIdentifier[metadataSourceComponent.toSingleModuleIdentifier()].orEmpty()
val fragmentsVisibleByParents =
parentResolutionsForDependency.filterIsInstance<ChooseVisibleSourceSetsImpl>()
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }