From 0f251477e9518b47d70542326d6047a17364d5fa Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Tue, 1 Feb 2022 18:11:07 +0400 Subject: [PATCH] KPM: reshape the API for setting up publications Accept a more structured requests for setting up publication. Allow passing lower-level overrides for publication settings of a particular configuration, which is needed to set up legacy targets' publications in KPM. --- .../mpp/pm20/util/ConfigurationUtils.kt | 100 ++++++++++++ .../plugin/mpp/pm20/variantPublishing.kt | 149 +++++++++++++----- .../kotlin/gradle/utils/providerApiUtils.kt | 3 + 3 files changed, 215 insertions(+), 37 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt new file mode 100644 index 00000000000..45234f1c458 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/util/ConfigurationUtils.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2010-2021 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.pm20.util + +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.capabilities.Capability +import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations + +internal fun copyConfigurationForPublishing( + project: Project, + newName: String, + configuration: Configuration, + overrideDependencies: (DependencySet.() -> Unit)? = null, + overrideArtifacts: (Configuration.(PublishArtifactSet) -> Unit)? = null, + overrideAttributes: (Configuration.(AttributeContainer) -> Unit)? = { + copyAttributes(it, attributes, it.keySet().minus(ProjectLocalConfigurations.ATTRIBUTE)) + }, + overrideCapabilities: (Configuration.(Collection) -> Unit)? = null, + configureAction: Configuration.() -> Unit = { } +) = copyConfiguration( + project, + newName, + configuration, + overrideDependencies, + overrideArtifacts, + overrideAttributes, + overrideCapabilities, + configureAction = { + isCanBeConsumed = false + isCanBeResolved = false + configureAction() + } +) + +internal fun copyConfiguration( + project: Project, + newName: String, + configuration: Configuration, + overrideDependencies: (DependencySet.() -> Unit)? = null, + overrideArtifacts: (Configuration.(PublishArtifactSet) -> Unit)? = null, + overrideAttributes: (Configuration.(AttributeContainer) -> Unit)? = null, + overrideCapabilities: (Configuration.(Collection) -> Unit)? = null, + configureAction: Configuration.() -> Unit = { } +): Configuration = + project.configurations.create(newName).apply { + isCanBeConsumed = configuration.isCanBeConsumed + isCanBeResolved = configuration.isCanBeResolved + + // dependencies: + if (overrideDependencies != null) { + withDependencies { dependencySet -> + overrideDependencies(dependencySet) + } + } else { + dependencies.addAllLater(project.listProperty { configuration.allDependencies }) + } + + // artifacts: + if (overrideArtifacts != null) { + overrideArtifacts(this, artifacts) + } else { + artifacts.addAllLater(project.listProperty { configuration.allArtifacts }) + } + + // attributes + if (overrideAttributes != null) { + overrideAttributes(this, attributes) + } else { + copyAttributes(configuration.attributes, attributes) + } + + // capabilities + if (overrideCapabilities != null) { + overrideCapabilities(this, configuration.outgoing.capabilities) + } else { + configuration.outgoing.capabilities.forEach(outgoing::capability) + } + + configureAction(this) + } + +internal fun copyAttributes(from: AttributeContainer, to: AttributeContainer, keys: Iterable> = from.keySet()) { + // capture type argument T + fun copyOneAttribute(from: AttributeContainer, to: AttributeContainer, key: Attribute) { + val value = checkNotNull(from.getAttribute(key)) + to.attribute(key, value) + } + for (key in keys) { + copyOneAttribute(from, to, key) + } +} + +internal inline fun Project.listProperty(noinline itemsProvider: () -> Iterable) = + objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/variantPublishing.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/variantPublishing.kt index bcdff6332f4..227fdc5ced7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/variantPublishing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/variantPublishing.kt @@ -14,12 +14,15 @@ import org.gradle.api.attributes.* import org.gradle.api.capabilities.Capability import org.gradle.api.component.AdhocComponentWithVariants import org.gradle.api.component.SoftwareComponentFactory +import org.gradle.api.provider.Provider import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.ComputedCapability +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyConfigurationForPublishing import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.disambiguateName import org.jetbrains.kotlin.gradle.plugin.mpp.publishedConfigurationName import org.jetbrains.kotlin.gradle.plugin.usageByName @@ -28,17 +31,67 @@ import org.jetbrains.kotlin.gradle.utils.dashSeparatedName import java.util.* import javax.inject.Inject +interface PlatformPublicationToMavenRequest { + val componentName: String + val fromModule: KotlinGradleModule + val publicationHolder: SingleMavenPublishedModuleHolder + val variantPublicationRequests: Iterable +} + +data class BasicPlatformPublicationToMavenRequest( + override val componentName: String, + override val fromModule: KotlinGradleModule, + override val publicationHolder: SingleMavenPublishedModuleHolder, + override val variantPublicationRequests: Iterable +) : PlatformPublicationToMavenRequest { + init { + check(variantPublicationRequests.all { it.fromVariant.containingModule === fromModule }) { + "Variants for publication should all belong to the fromModule ($fromModule)" + } + } +} + +/** TODO: consider also using this class for exposing a KPM variant's configurations for project-to-project dependencies, + * so that a variant may expose an arbitrary set of configurations rather just { API, runtime } or { API } */ +interface VariantPublicationRequest { + val fromVariant: KotlinGradleVariant + val publishConfiguration: Configuration +} + +data class BasicVariantPublicationRequest( + override val fromVariant: KotlinGradleVariant, + override val publishConfiguration: Configuration +) : VariantPublicationRequest + + fun VariantPublishingConfigurator.configureNativeVariantPublication(variant: KotlinNativeVariantInternal) { val publishConfigurations = listOfNotNull( - variant.apiElementsConfiguration.name, - variant.hostSpecificMetadataElementsConfiguration?.name // host-specific metadata may be absent + variant.apiElementsConfiguration, + variant.hostSpecificMetadataElementsConfiguration // host-specific metadata may be absent ) - configurePublishing(variant, variant, publishConfigurations) + configureSingleVariantPublishing(variant, variant, publishConfigurations) } fun VariantPublishingConfigurator.configureSingleVariantPublication(variant: KotlinGradlePublishedVariantWithRuntime) { - val publishConfigurations = listOf(variant.apiElementsConfiguration.name, variant.runtimeElementsConfiguration.name) - configurePublishing(variant, variant, publishConfigurations) + val publishConfigurations = listOf(variant.apiElementsConfiguration, variant.runtimeElementsConfiguration) + configureSingleVariantPublishing(variant, variant, publishConfigurations) +} + +fun VariantPublishingConfigurator.configureSingleVariantPublishing( + variant: KotlinGradleVariant, + publishedModuleHolder: SingleMavenPublishedModuleHolder, + publishConfigurations: Iterable +) { + configurePublishing( + BasicPlatformPublicationToMavenRequest( + platformComponentName(variant), + variant.containingModule, + publishedModuleHolder, + publishConfigurations.map { + BasicVariantPublicationRequest(variant, it) + } + ) + ) } open class VariantPublishingConfigurator @Inject constructor( @@ -60,34 +113,39 @@ open class VariantPublishingConfigurator @Inject constructor( } open fun configurePublishing( - variant: KotlinGradleVariant, - publishedModuleHolder: SingleMavenPublishedModuleHolder, - publishConfigurations: Iterable + request: PlatformPublicationToMavenRequest ) { - val componentName = platformComponentName(variant) - val configurationsMap = publishConfigurations.associateWith { inferMavenScope(variant, it) } + val componentName = request.componentName registerPlatformModulePublication( componentName, - publishedModuleHolder, - configurationsMap, - variant.containingModule::ifMadePublic + request.publicationHolder, + request.variantPublicationRequests, + request.fromModule::ifMadePublic ) - /** The MPP plugin doesn't publish the source artifacts as variants; keep that behavior for legacymapped variants */ - if (variant !is LegacyMappedVariant) - configureSourceElementsPublishing(variant) + val publishFromVariants = request.variantPublicationRequests.mapTo(mutableSetOf()) { it.fromVariant } + + // Collecting sources for multiple variants is not yet supported; + // TODO make callers provide the source variants? + // The MPP plugin doesn't publish the source artifacts as variants; keep that behavior for legacy-mapped variants for now + if ( + publishFromVariants.size == 1 && + publishFromVariants.none { it is LegacyMappedVariant } + ) { + val singlePublishedVariant = publishFromVariants.single() + configureSourceElementsPublishing(componentName, singlePublishedVariant) + } registerPlatformVariantsInRootModule( - publishedModuleHolder, - variant.containingModule, - publishConfigurations + request.publicationHolder, + request.fromModule, + request.variantPublicationRequests ) } - protected open fun configureSourceElementsPublishing(variant: KotlinGradleVariant) { + protected open fun configureSourceElementsPublishing(componentName: String, variant: KotlinGradleVariant) { val configurationName = variant.disambiguateName("sourceElements") - val componentName = platformComponentName(variant) val docsVariants = DocumentationVariantConfigurator().createSourcesElementsConfiguration(configurationName, variant) project.components.withType(AdhocComponentWithVariants::class.java).named(componentName).configure { component -> component.addVariantsFromConfiguration(docsVariants) { } @@ -102,13 +160,29 @@ open class VariantPublishingConfigurator @Inject constructor( protected open fun registerPlatformModulePublication( componentName: String, publishedModuleHolder: SingleMavenPublishedModuleHolder, - publishConfigurationsWithMavenScopes: Map, + variantRequests: Iterable, whenShouldRegisterPublication: (() -> Unit) -> Unit ) { val platformComponent = softwareComponentFactory.adhoc(componentName) project.components.add(platformComponent) - publishConfigurationsWithMavenScopes.forEach { (configurationName, mavenScopeOrNull) -> - platformComponent.addVariantsFromConfiguration(project.configurations.getByName(configurationName)) details@{ variantDetails -> + + variantRequests.forEach { request -> + val originalConfiguration = request.publishConfiguration + val mavenScopeOrNull = inferMavenScope(request.fromVariant, originalConfiguration.name) + + val publishedConfiguration = copyConfigurationForPublishing( + request.fromVariant.project, + newName = publishedConfigurationName(originalConfiguration.name) + "-platform", + configuration = originalConfiguration, + overrideArtifacts = (request as? AdvancedVariantPublicationRequest) + ?.overrideConfigurationArtifactsForPublication + ?.let { override -> { artifacts -> artifacts.addAllLater(override) } }, + overrideAttributes = (request as? AdvancedVariantPublicationRequest) + ?.overrideConfigurationAttributesForPublication + ?.let { override -> { attributes -> copyAttributes(override, attributes) } } + ) + + platformComponent.addVariantsFromConfiguration(publishedConfiguration) details@{ variantDetails -> // TODO: This is temporary for Android! Make sure to only publish AAR val androidArtifactType = variantDetails.configurationVariant.attributes.getAttribute(AndroidArtifacts.ARTIFACT_TYPE) if (androidArtifactType != null && androidArtifactType != AndroidArtifacts.ArtifactType.AAR.type) { @@ -138,7 +212,7 @@ open class VariantPublishingConfigurator @Inject constructor( protected open fun registerPlatformVariantsInRootModule( publishedModuleHolder: SingleMavenPublishedModuleHolder, kotlinModule: KotlinGradleModule, - configurationNames: Iterable + variantRequests: Iterable ) { val platformModuleDependencyProvider = project.provider { val coordinates = publishedModuleHolder.publishedMavenModuleCoordinates @@ -154,28 +228,29 @@ open class VariantPublishingConfigurator @Inject constructor( .withType(AdhocComponentWithVariants::class.java) .getByName(rootPublicationComponentName(kotlinModule)) - configurationNames.forEach { configurationName -> - fun copyAttribute(from: AttributeContainer, to: AttributeContainer, key: Attribute) { - to.attribute(key, checkNotNull(from.getAttribute(key))) - } - - val originalConfiguration = project.configurations.getByName(configurationName) - project.configurations.create(publishedConfigurationName(configurationName)).apply { + variantRequests.forEach { variantRequest -> + val configuration = variantRequest.publishConfiguration + project.configurations.create(publishedConfigurationName(configuration.name)).apply { isCanBeConsumed = false isCanBeResolved = false setModuleCapability(this, kotlinModule) dependencies.addLater(platformModuleDependencyProvider) - val originalAttributes = originalConfiguration.attributes - originalAttributes.keySet().forEach { - copyAttribute(originalAttributes, attributes, it) - } + copyAttributes(configuration.attributes, this.attributes) rootSoftwareComponent.addVariantsFromConfiguration(this) { } } } } } +internal data class AdvancedVariantPublicationRequest( + override val fromVariant: KotlinGradleVariant, + override val publishConfiguration: Configuration, + val overrideConfigurationAttributesForPublication: AttributeContainer?, + val overrideConfigurationArtifactsForPublication: Provider>?, + val includeIntoProjectStructureMetadata: Boolean +) : VariantPublicationRequest + open class DocumentationVariantConfigurator { open fun createSourcesElementsConfiguration( project: Project, @@ -214,4 +289,4 @@ open class DocumentationVariantConfigurator { ComputedCapability.fromModuleOrNull(variant.containingModule) ) } -} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt index 275a8e32dd7..a7a80261ee1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/providerApiUtils.kt @@ -97,3 +97,6 @@ internal fun Project.filesProvider( internal fun T.outputFilesProvider(provider: T.() -> Any): ConfigurableFileCollection { return project.filesProvider(this) { provider() } } + +internal inline fun Project.listProperty(noinline itemsProvider: () -> Iterable) = + objects.listProperty(T::class.java).apply { set(provider(itemsProvider)) } \ No newline at end of file