All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.gradle.plugin.mpp.ResolvedMppVariantsProvider.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * 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.gradle.plugin.mpp

import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.artifacts.result.ResolvedComponentResult
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.gradle.utils.getOrPut
import org.jetbrains.kotlin.project.model.KotlinModuleIdentifier
import java.io.File

internal class ResolvedMppVariantsProvider private constructor(private val project: Project) {
    companion object {
        fun get(project: Project): ResolvedMppVariantsProvider {
            val propertyName = "kotlin.mpp.internal.resolvedModuleVariantsProvider"
            return project.extensions.extraProperties.getOrPut(propertyName) {
                ResolvedMppVariantsProvider(project)
            }
        }
    }

    /** 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: KotlinModuleIdentifier, configuration: Configuration): String? =
        getEntryForModule(moduleIdentifier).run {
            if (configuration !in resolvedVariantsByConfiguration) {
                resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NONE)
            }
            val variants = resolvedVariantsByConfiguration.getOrPut(configuration) { null }
            variants?.singleOrNull()?.displayName
        }

    /** Gets the artifact that contains the common code metadata for the given [rootModuleIdentifier], which can only denote the root
     * 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: KotlinModuleIdentifier, configuration: Configuration): File? {
        val rootModuleEntry = getEntryForModule(rootModuleIdentifier)

        val platformModuleEntry = rootModuleEntry.run {
            if (configuration !in chosenPlatformModuleByConfiguration) {
                resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
            }
            // At this point the map should contain the result if calculation above succeeded. If not, put null to avoid recalculation.
            chosenPlatformModuleByConfiguration.getOrPut(configuration) { null }
        }

        return platformModuleEntry?.run {
            // The condition might be true if the configuration has only been resolved with resolution mode NORMAL
            if (configuration !in resolvedMetadataArtifactByConfiguration) {
                resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
            }
            // At this point the map should contain the result if calculation above succeeded. If not, put null to avoid recalculation.
            resolvedMetadataArtifactByConfiguration.getOrPut(configuration) { null }
        }
    }

    /** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */
    fun getResolvedArtifactByPlatformModule(moduleIdentifier: KotlinModuleIdentifier, configuration: Configuration): File? =
        getEntryForModule(moduleIdentifier).run {
            if (configuration !in resolvedArtifactByConfiguration) {
                resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
            }
            // At this point the map should contain the result if the calculation above succeeded. If not, put null to avoid recalculation.
            resolvedArtifactByConfiguration.getOrPut(configuration) { null }
        }

    private fun getEntryForModule(moduleIdentifier: KotlinModuleIdentifier) =
        entriesCache.getOrPut(moduleIdentifier, { ModuleEntry(moduleIdentifier) })

    private val entriesCache: MutableMap = mutableMapOf()

    private val mppComponentsByConfiguration: MutableMap> = mutableMapOf()

    private enum class ArtifactResolutionMode {
        NONE, NORMAL, METADATA
    }

    private fun resolveConfigurationAndSaveVariants(
        configuration: Configuration,
        artifactResolutionMode: ArtifactResolutionMode
    ) {
        val mppComponentIds: Set = mppComponentsByConfiguration.getOrPut(configuration) {
            resolveMppComponents(configuration)
        }

        if (artifactResolutionMode != ArtifactResolutionMode.NONE) {
            val artifacts = resolveArtifacts(artifactResolutionMode, configuration, mppComponentIds)
            matchMppComponentsWithResolvedArtifacts(mppComponentIds, artifacts, configuration, artifactResolutionMode)
        }
    }

    private fun resolveMppComponents(configuration: Configuration): Set {
        val result = mutableListOf()

        configuration.incoming.resolutionResult.allComponents { component ->
            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)
                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()
                            .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
                        }
                    }
                }
            }
        }

        return result.toSet()
    }

    private fun resolveArtifacts(
        artifactResolutionMode: ArtifactResolutionMode,
        configuration: Configuration,
        mppComponents: Set
    ): Map {
        val mppComponentById = mppComponents.associateBy { it.id }

        val artifactsConfiguration =
            if (
                artifactResolutionMode == ArtifactResolutionMode.NORMAL ||
                configuration.attributes.getAttribute(Usage.USAGE_ATTRIBUTE)?.name == KotlinUsages.KOTLIN_METADATA
            ) {
                configuration
            } else {
                configuration.copyRecursive().apply {
                    attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA))
                }
            }

        return artifactsConfiguration.incoming.artifactView { view ->
            view.componentFilter { it in mppComponentById }
            view.attributes { attrs -> attrs.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) }
            view.lenient(true)
        }.artifacts.associateBy { mppComponentById.getValue(it.id.componentIdentifier) }
    }

    private fun matchMppComponentsWithResolvedArtifacts(
        mppComponentIds: Set,
        artifacts: Map,
        configuration: Configuration,
        artifactResolutionMode: ArtifactResolutionMode
    ) {
        val mppModuleIds = mppComponentIds.flatMapTo(mutableSetOf()) { componentId ->
            componentId.toModuleIdentifiers()
        }

        mppComponentIds.forEach { componentId ->
            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
                    }

                    // 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)
                        }
                    }
                }
            }
        }
    }

    /**
     * Stores resolution results of the module denoted by [moduleIdentifier] in different configurations of the project.
     * The [moduleIdentifier] may point to a root module of a multiplatform project (then it has meaningful
     * [chosenPlatformModuleByConfiguration]) or to a platform module.
     */
    private class ModuleEntry(
        @Suppress("unused") // simplify debugging
        val moduleIdentifier: KotlinModuleIdentifier
    ) {
        val dependenciesByConfiguration: MutableMap> = HashMap()
        val resolvedVariantsByConfiguration: MutableMap?> = HashMap()
        val resolvedArtifactByConfiguration: MutableMap = HashMap()
        val resolvedMetadataArtifactByConfiguration: MutableMap = HashMap()
        val chosenPlatformModuleByConfiguration: MutableMap = HashMap()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy