Skip to content

Commit

Permalink
Set up multiplatform publishing maintaining jvm artifact name
Browse files Browse the repository at this point in the history
  • Loading branch information
serjsysoev committed Sep 4, 2024
1 parent bc94c2f commit 5d7e023
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 24 deletions.
95 changes: 71 additions & 24 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}

repositories {
gradlePluginPortal()
}
Original file line number Diff line number Diff line change
@@ -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<ComponentsFactoryAccess>().factory
.also { project.extra["_componentFactory"] = it }

fun copyAttributes(from: AttributeContainer, to: AttributeContainer) {
// capture type argument T
fun <T : Any> copyOneAttribute(from: AttributeContainer, to: AttributeContainer, key: Attribute<T>) {
val value = checkNotNull(from.getAttribute(key))
to.attribute(key, value)
}
for (key in from.keySet()) {
copyOneAttribute(from, to, key)
}
}

class MultiModuleMavenPublishingConfiguration {
val modules = mutableMapOf<String, Module>()

class Module(val name: String) {
val variants = mutableMapOf<String, Variant>()
val includes = mutableSetOf<Module>()

class Variant(
val configurationName: String
) {
var name: String = configurationName
val attributesConfigurations = mutableListOf<AttributeContainer.() -> Unit>()
fun attributes(code: AttributeContainer.() -> Unit) {
attributesConfigurations += code
}

val artifactsWithConfigurations = mutableListOf<Pair<Any, ConfigurablePublishArtifact.() -> Unit>>()
fun artifact(file: Any, code: ConfigurablePublishArtifact.() -> Unit = {}) {
artifactsWithConfigurations += file to code
}

val configurationConfigurations = mutableListOf<Configuration.() -> Unit>()
fun configuration(code: Configuration.() -> Unit) {
configurationConfigurations += code
}

val variantDetailsConfigurations = mutableListOf<ConfigurationVariantDetails.() -> Unit>()
fun configureVariantDetails(code: ConfigurationVariantDetails.() -> Unit) {
variantDetailsConfigurations += code
}

var suppressPomMetadataWarnings: Boolean = false
fun suppressPomMetadataWarnings() { suppressPomMetadataWarnings = true }
}

val mavenPublicationConfigurations = mutableListOf<MavenPublication.() -> 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<PublishingExtension>().publications
for ((componentName, component) in mergedComponents) {
publicationsContainer.create<MavenPublication>(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<String, String>,
private val base: SoftwareComponentInternal
): SoftwareComponentInternal by base {

override fun getName(): String = base.name
override fun getUsages(): Set<UsageContext> {
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<SoftwareComponent>
) : ComponentWithVariants, SoftwareComponentInternal {
override fun getName(): String = mainComponent.name

override fun getUsages(): Set<UsageContext> = (mainComponent as SoftwareComponentInternal).usages

override fun getVariants(): Set<SoftwareComponent> = externalComponents
}

fun ConfigurationContainer.getOrCreate(name: String): Configuration = findByName(name) ?: create(name)
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 5d7e023

Please sign in to comment.