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

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

There is a newer version: 2.1.0-RC
Show newest version
/*
 * Copyright 2010-2018 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.*
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.attributes.Usage
import org.gradle.api.capabilities.Capability
import org.gradle.api.component.ComponentWithCoordinates
import org.gradle.api.component.ComponentWithVariants
import org.gradle.api.component.SoftwareComponent
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext
import org.gradle.api.provider.SetProperty
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.metadataTarget
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterFinaliseCompilations
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.attributes.KlibPackaging
import org.jetbrains.kotlin.gradle.plugin.await
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinUsageContext.PublishOnlyIf
import org.jetbrains.kotlin.gradle.plugin.mpp.publishing.kotlinMultiplatformRootPublication
import org.jetbrains.kotlin.gradle.targets.metadata.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly

abstract class KotlinSoftwareComponent(
    private val project: Project,
    private val name: String,
    protected val kotlinTargets: Iterable,
) : SoftwareComponentInternal, ComponentWithVariants {

    override fun getName(): String = name

    private val metadataTarget get() = project.multiplatformExtension.metadataTarget

    private val _variants = project.future {
        AfterFinaliseCompilations.await()
        kotlinTargets
            .filter { target -> target !is KotlinMetadataTarget }
            .flatMap { target ->
                val targetPublishableComponentNames = target.internal.kotlinComponents
                    .filter { component -> component.publishable }
                    .map { component -> component.name }
                    .toSet()

                target.components.filter { it.name in targetPublishableComponentNames }
            }.toSet()
    }

    override fun getVariants(): Set = _variants.getOrThrow()

    private val _usages: Future> = project.future {
        metadataTarget.awaitMetadataCompilationsCreated()

        if (!project.isKotlinGranularMetadataEnabled) {
            val metadataCompilation = metadataTarget.compilations.getByName(MAIN_COMPILATION_NAME)
            return@future metadataTarget.createUsageContexts(metadataCompilation)
        }

        mutableSetOf().apply {
            val allMetadataJar = project.tasks.named(KotlinMetadataTargetConfigurator.ALL_METADATA_JAR_NAME)
            val allMetadataArtifact = project.artifacts.add(Dependency.ARCHIVES_CONFIGURATION, allMetadataJar) { allMetadataArtifact ->
                allMetadataArtifact.classifier = if (project.isCompatibilityMetadataVariantEnabled) "all" else ""
            }

            this += DefaultKotlinUsageContext(
                compilation = metadataTarget.compilations.getByName(MAIN_COMPILATION_NAME),
                mavenScope = KotlinUsageContext.MavenScope.COMPILE,
                dependencyConfigurationName = metadataTarget.apiElementsConfigurationName,
                overrideConfigurationArtifacts = project.setProperty { listOf(allMetadataArtifact) }
            )

            if (project.isCompatibilityMetadataVariantEnabled) {
                // Ensure that consumers who expect Kotlin 1.2.x metadata package can still get one:
                // publish the old metadata artifact:
                this += run {
                    DefaultKotlinUsageContext(
                        metadataTarget.compilations.getByName(MAIN_COMPILATION_NAME),
                        KotlinUsageContext.MavenScope.COMPILE,
                        /** this configuration is created by [KotlinMetadataTargetConfigurator.createCommonMainElementsConfiguration] */
                        COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME
                    )
                }
            }

            val sourcesElements = metadataTarget.sourcesElementsConfigurationName
            if (metadataTarget.isSourcesPublishable) {
                addSourcesJarArtifactToConfiguration(sourcesElements)
                this += DefaultKotlinUsageContext(
                    compilation = metadataTarget.compilations.getByName(MAIN_COMPILATION_NAME),
                    dependencyConfigurationName = sourcesElements,
                    includeIntoProjectStructureMetadata = false,
                    publishOnlyIf = { metadataTarget.isSourcesPublishable }
                )
            }
        }
    }


    override fun getUsages(): Set {
        return _usages.getOrThrow().publishableUsages()
    }

    private suspend fun allPublishableCommonSourceSets() = getCommonSourceSetsForMetadataCompilation(project) +
            getHostSpecificMainSharedSourceSets(project)

    /**
     * Registration (during object init) of [sourcesJarTask] is required for cases when
     * user build scripts want to have access to sourcesJar task to configure it
     */
    private val sourcesJarTask: TaskProvider = sourcesJarTaskNamed(
        "sourcesJar",
        name,
        project,
        project.future { allPublishableCommonSourceSets().associate { it.name to it.kotlin } },
        name.toLowerCaseAsciiOnly()
    )

    private fun addSourcesJarArtifactToConfiguration(configurationName: String): PublishArtifact {
        return project.artifacts.add(configurationName, sourcesJarTask) { sourcesJarArtifact ->
            sourcesJarArtifact.classifier = "sources"
        }
    }

    val publicationDelegate: MavenPublication? get() = project.kotlinMultiplatformRootPublication.lenient.getOrNull()
}

class KotlinSoftwareComponentWithCoordinatesAndPublication(project: Project, name: String, kotlinTargets: Iterable) :
    KotlinSoftwareComponent(project, name, kotlinTargets), ComponentWithCoordinates {

    override fun getCoordinates(): ModuleVersionIdentifier = getCoordinatesFromPublicationDelegateAndProject(
        publicationDelegate, kotlinTargets.first().project, null
    )
}

interface KotlinUsageContext : UsageContext {
    val compilation: KotlinCompilation<*>
    val dependencyConfigurationName: String
    val includeIntoProjectStructureMetadata: Boolean
    val mavenScope: MavenScope?

    enum class MavenScope {
        COMPILE, RUNTIME;
    }
}

class DefaultKotlinUsageContext(
    override val compilation: KotlinCompilation<*>,
    override val mavenScope: KotlinUsageContext.MavenScope? = null,
    override val dependencyConfigurationName: String,
    internal val overrideConfigurationArtifacts: SetProperty? = null,
    internal val overrideConfigurationAttributes: AttributeContainer? = null,
    override val includeIntoProjectStructureMetadata: Boolean = true,
    internal val publishOnlyIf: PublishOnlyIf = PublishOnlyIf { true },
) : KotlinUsageContext {
    fun interface PublishOnlyIf {
        fun predicate(): Boolean
    }

    private val kotlinTarget: KotlinTarget get() = compilation.target
    private val project: Project get() = kotlinTarget.project

    @Deprecated(
        message = "Usage is no longer supported. Use `usageScope`",
        replaceWith = ReplaceWith("usageScope"),
        level = DeprecationLevel.ERROR
    )
    override fun getUsage(): Usage = error("Usage is no longer supported. Use `usageScope`")

    override fun getName(): String = dependencyConfigurationName

    private val configuration: Configuration
        get() = project.configurations.getByName(dependencyConfigurationName)

    override fun getDependencies(): MutableSet =
        configuration.incoming.dependencies.withType(ModuleDependency::class.java)

    override fun getDependencyConstraints(): MutableSet =
        configuration.incoming.dependencyConstraints

    override fun getArtifacts(): Set =
        overrideConfigurationArtifacts?.get()?.toSet() ?:
        // TODO Gradle Java plugin does that in a different way; check whether we can improve this
        configuration.artifacts

    override fun getAttributes(): AttributeContainer {
        val configurationAttributes = overrideConfigurationAttributes ?: configuration.attributes

        /** TODO Using attributes of a detached configuration is a small and 'conservative' fix for KT-29758, [HierarchyAttributeContainer]
         * being rejected by Gradle 5.2+; we may need to either not filter the attributes, which will lead to
         * [ProjectLocalConfigurations.ATTRIBUTE] being published in the Gradle module metadata, which will potentially complicate our
         * attributes schema migration, or create proper, non-detached configurations for publishing that are separated from the
         * configurations used for project-to-project dependencies
         */
        val result = project.configurations.detachedResolvable().attributes

        configurationAttributes.copyAttributesTo(
            project.providers,
            dest = result,
            keys = filterOutNonPublishableAttributes(configurationAttributes.keySet())
        )

        return result
    }

    override fun getCapabilities(): Set = emptySet()

    override fun getGlobalExcludes(): Set = emptySet()

    private val publishJvmEnvironmentAttribute get() = project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute

    private fun filterOutNonPublishableAttributes(attributes: Set>): Set> =
        attributes.filterTo(mutableSetOf()) {
            it != ProjectLocalConfigurations.ATTRIBUTE &&
                    /**
                     * We exclude the attribute "org.gradle.jvm.environment" from publishing to avoid two issues:
                     *
                     * 1. Kotlin < 1.6.0 consumers which don't set this attribute on the consumer side. If this attribute is not set on the
                     * consumer side, then the Gradle built-in disambiguation rule applies: { standard-jvm, android } -> standard-jvm.
                     * In Kotlin 1.5.31, this would conflict with the rule on o.j.k.platform.type: { androidJvm, jvm } -> androidJvm, so the
                     * two rules would choose different closes match variants, and disambiguation would fail.
                     *
                     * 2. If this attribute is published, but not present on all the variants in a multiplatform library, and is also
                     * missing on the consumer side (like Gradle < 7.0, Kotlin 1.6.0), then there is a
                     * case when Gradle fails to choose a variant in a completely reasonable setup.
                     *
                     * UPD: 1.9.20:
                     * We should now be ready to publish the 'jvm environment' attribute.
                     * It will however be rolled out as 'opt-in' first (as safety measure).
                     * We expect that the 'external Android target' will opt-into publishing this attribute,
                     * as it will switch to KotlinPlatformType.jvm and requires this additional attribute to disambiguate
                     * Android from the JVM
                     */
                    (it.name != "org.gradle.jvm.environment" || publishJvmEnvironmentAttribute) &&
                    /**
                     * Non-packed klibs are used only locally and should not be published.
                     * Thus, it does not make sense to publish this attribute as well.
                     *
                     * Another option could be to put this attribute only on the secondary variant that is non-packed.
                     * However, disambiguation rules do not work well on old Gradle versions with this.
                     */
                    it.name != KlibPackaging.ATTRIBUTE.name
        }

}

internal fun Iterable.publishableUsages() = this
    .filter { it.publishOnlyIf.predicate() }
    .toSet()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy