From 5d7e0230b12d4f5f59e95fbfb1fd961febfc6ba7 Mon Sep 17 00:00:00 2001 From: Sergei Sysoev Date: Wed, 4 Sep 2024 12:49:21 +0200 Subject: [PATCH] Set up multiplatform publishing maintaining jvm artifact name --- build.gradle.kts | 95 ++++++-- buildSrc/build.gradle.kts | 7 + .../publishing/CustomVariantPublishingDsl.kt | 206 ++++++++++++++++++ gradle.properties | 2 + 4 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/plugins/publishing/CustomVariantPublishingDsl.kt diff --git a/build.gradle.kts b/build.gradle.kts index 27ace67..fa9c060 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import jetbrains.sign.GpgSignSignatoryProvider import org.gradle.jvm.tasks.Jar import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import plugins.publishing.* /* * Copyright 2000-2021 JetBrains s.r.o. @@ -37,6 +38,7 @@ plugins { id("io.github.gradle-nexus.publish-plugin") version "1.1.0" `maven-publish` signing + java } var projectVersion = project.findProperty("projectVersion") as String @@ -180,6 +182,15 @@ tasks { into("META-INF/versions/9/") } } + + val allMetadataJar by existing(Jar::class) { + archiveClassifier.set("all") + } + + val javadocJar by creating(Jar::class) { + from(javadoc) + archiveClassifier.set("javadoc") + } } nexusPublishing { @@ -192,33 +203,69 @@ nexusPublishing { } publishing { - publications.withType(MavenPublication::class) { - group = "org.jetbrains" - version = rootProject.version as String - - pom { - name.set("JetBrains Java Annotations") - description.set("A set of annotations used for code inspection support and code documentation.") - url.set("https://github.com/JetBrains/java-annotations") - scm { - url.set("https://github.com/JetBrains/java-annotations") - connection.set("scm:git:git://github.com/JetBrains/java-annotations.git") - developerConnection.set("scm:git:ssh://github.com:JetBrains/java-annotations.git") + val artifactBaseName = base.archivesName.get() + configureMultiModuleMavenPublishing { + val rootModule = module("rootModule") { + mavenPublication { + artifactId = artifactBaseName + groupId = "org.jetbrains" + configureKotlinPomAttributes(packaging = "jar") + artifact(tasks.getByName("javadocJar")) } - licenses { - license { - name.set("The Apache Software License, Version 2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") - distribution.set("repo") - } + variant("metadataApiElements") { suppressPomMetadataWarnings() } + variant("jvmApiElements") + variant("jvmRuntimeElements") { + configureVariantDetails { mapToMavenScope("runtime") } } - developers { - developer { - id.set("JetBrains") - name.set("JetBrains Team") - organization.set("JetBrains") - organizationUrl.set("https://www.jetbrains.com") + variant("jvmSourcesElements") + } + val targetModules = kotlin.targets.filter { it.targetName != "jvm" && it.targetName != "metadata" }.map { target -> + val targetName = target.targetName + module("${targetName}Module") { + mavenPublication { + artifactId = "$artifactBaseName-$targetName" + groupId = "org.jetbrains" + configureKotlinPomAttributes(packaging = "klib") } + variant("${targetName}ApiElements") + if (configurations.findByName("${targetName}RuntimeElements") != null) { + variant("${targetName}RuntimeElements") + } + variant("${targetName}SourcesElements") + } + } + + // Makes all variants from accompanying artifacts visible through `available-at` + rootModule.include(*targetModules.toTypedArray()) + } +} + +fun MavenPublication.configureKotlinPomAttributes( + packaging: String, +) { + pom { + this.packaging = packaging + name.set("JetBrains Java Annotations") + description.set("A set of annotations used for code inspection support and code documentation.") + url.set("https://github.com/JetBrains/java-annotations") + scm { + url.set("https://github.com/JetBrains/java-annotations") + connection.set("scm:git:git://github.com/JetBrains/java-annotations.git") + developerConnection.set("scm:git:ssh://github.com:JetBrains/java-annotations.git") + } + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + developers { + developer { + id.set("JetBrains") + name.set("JetBrains Team") + organization.set("JetBrains") + organizationUrl.set("https://www.jetbrains.com") } } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..cc02e63 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} diff --git a/buildSrc/src/main/kotlin/plugins/publishing/CustomVariantPublishingDsl.kt b/buildSrc/src/main/kotlin/plugins/publishing/CustomVariantPublishingDsl.kt new file mode 100644 index 0000000..0a65dee --- /dev/null +++ b/buildSrc/src/main/kotlin/plugins/publishing/CustomVariantPublishingDsl.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2023 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.txt file. + */ + +package plugins.publishing + +import org.gradle.api.Project +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.component.* +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.newInstance + +private open class ComponentsFactoryAccess +@javax.inject.Inject +constructor(val factory: SoftwareComponentFactory) + +val Project.componentFactory: SoftwareComponentFactory + get() = findProperty("_componentFactory") as SoftwareComponentFactory? + ?: objects.newInstance().factory + .also { project.extra["_componentFactory"] = it } + +fun copyAttributes(from: AttributeContainer, to: AttributeContainer) { + // 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 from.keySet()) { + copyOneAttribute(from, to, key) + } +} + +class MultiModuleMavenPublishingConfiguration { + val modules = mutableMapOf() + + class Module(val name: String) { + val variants = mutableMapOf() + val includes = mutableSetOf() + + class Variant( + val configurationName: String + ) { + var name: String = configurationName + val attributesConfigurations = mutableListOf Unit>() + fun attributes(code: AttributeContainer.() -> Unit) { + attributesConfigurations += code + } + + val artifactsWithConfigurations = mutableListOf Unit>>() + fun artifact(file: Any, code: ConfigurablePublishArtifact.() -> Unit = {}) { + artifactsWithConfigurations += file to code + } + + val configurationConfigurations = mutableListOf Unit>() + fun configuration(code: Configuration.() -> Unit) { + configurationConfigurations += code + } + + val variantDetailsConfigurations = mutableListOf Unit>() + fun configureVariantDetails(code: ConfigurationVariantDetails.() -> Unit) { + variantDetailsConfigurations += code + } + + var suppressPomMetadataWarnings: Boolean = false + fun suppressPomMetadataWarnings() { suppressPomMetadataWarnings = true } + } + + val mavenPublicationConfigurations = mutableListOf Unit>() + fun mavenPublication(code: MavenPublication.() -> Unit) { + mavenPublicationConfigurations += code + } + + fun variant(fromConfigurationName: String, code: Variant.() -> Unit = {}): Variant { + val variant = variants.getOrPut(fromConfigurationName) { Variant(fromConfigurationName) } + variant.code() + return variant + } + + fun include(vararg modules: Module) { + includes.addAll(modules) + } + } + + fun module(name: String, code: Module.() -> Unit): Module { + val module = modules.getOrPut(name) { Module(name) } + module.code() + return module + } +} + +fun Project.configureMultiModuleMavenPublishing(code: MultiModuleMavenPublishingConfiguration.() -> Unit) { + val publishingConfiguration = MultiModuleMavenPublishingConfiguration() + publishingConfiguration.code() + + val components = publishingConfiguration + .modules + .mapValues { (_, module) -> project.createModulePublication(module) } + + val componentsWithExternals = publishingConfiguration + .modules + .filter { (_, module) -> module.includes.isNotEmpty() } + .mapValues { (moduleName, module) -> + val mainComponent = components[moduleName] ?: error("Component with name $moduleName wasn't created") + val externalComponents = module.includes + .map { components[it.name] ?: error("Component with name ${it.name} wasn't created") } + .toSet() + ComponentWithExternalVariants(mainComponent, externalComponents) + } + + // override some components with items from componentsWithExternals + val mergedComponents = components + componentsWithExternals + + val publicationsContainer = project.extensions.getByType().publications + for ((componentName, component) in mergedComponents) { + publicationsContainer.create(componentName) { + from(component) + val module = publishingConfiguration.modules[componentName]!! + module.mavenPublicationConfigurations.forEach { configure -> configure() } + module.variants.values.filter { it.suppressPomMetadataWarnings }.forEach { + suppressPomMetadataWarningsFor(it.name) + } + } + } +} + + +fun Project.createModulePublication(module: MultiModuleMavenPublishingConfiguration.Module): SoftwareComponent { + val component = componentFactory.adhoc(module.name) + module.variants.values.forEach { addVariant(component, it) } + + val newNames = module.variants.map { it.key to it.value.name }.filter { it.first != it.second }.toMap() + return if (newNames.isNotEmpty()) { + ComponentWithRenamedVariants(newNames, component as SoftwareComponentInternal) + } else { + component + } +} + +fun Project.addVariant(component: AdhocComponentWithVariants, variant: MultiModuleMavenPublishingConfiguration.Module.Variant) { + val configuration: Configuration = configurations.getOrCreate(variant.configurationName) + configuration.apply { + isCanBeResolved = false + + variant.attributesConfigurations.forEach { configure -> attributes.configure() } + } + + for ((artifactNotation, configure) in variant.artifactsWithConfigurations) { + artifacts.add(configuration.name, artifactNotation) { + configure() + } + } + + for (configure in variant.configurationConfigurations) { + configuration.apply(configure) + } + + component.addVariantsFromConfiguration(configuration) { + variant.variantDetailsConfigurations.forEach { configure -> configure() } + } +} + +private class RenamedVariant(val newName: String, context: UsageContext) : UsageContext by context { + override fun getName(): String = newName +} + +private class ComponentWithRenamedVariants( + val newNames: Map, + private val base: SoftwareComponentInternal +): SoftwareComponentInternal by base { + + override fun getName(): String = base.name + override fun getUsages(): Set { + return base.usages.map { + val newName = newNames[it.name] + if (newName != null) { + RenamedVariant(newName, it) + } else { + it + } + }.toSet() + } +} + +private class ComponentWithExternalVariants( + private val mainComponent: SoftwareComponent, + private val externalComponents: Set +) : ComponentWithVariants, SoftwareComponentInternal { + override fun getName(): String = mainComponent.name + + override fun getUsages(): Set = (mainComponent as SoftwareComponentInternal).usages + + override fun getVariants(): Set = externalComponents +} + +fun ConfigurationContainer.getOrCreate(name: String): Configuration = findByName(name) ?: create(name) \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fe69cb5..c906938 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,3 +18,5 @@ projectVersion=25.0.0 kotlin.code.style=official kotlin.mpp.stability.nowarn=true kotlin.stdlib.default.dependency=false +kotlin.internal.mpp.createDefaultMultiplatformPublications=false +kotlin.native.ignoreIncorrectDependencies=true \ No newline at end of file