1 @file:Suppress("UnstableApiUsage")
2
3 import groovy.util.Node
4 import groovy.util.NodeList
5 import org.gradle.api.Project
6 import org.gradle.api.XmlProvider
7 import org.gradle.api.artifacts.dsl.*
8 import org.gradle.api.publish.PublishingExtension
9 import org.gradle.api.publish.maven.*
10 import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
11 import org.gradle.api.tasks.*
12 import org.gradle.api.tasks.bundling.Jar
13 import org.gradle.kotlin.dsl.*
14 import org.gradle.plugins.signing.*
15 import java.net.*
16
17 // Pom configuration
18
configureMavenCentralMetadatanull19 fun MavenPom.configureMavenCentralMetadata(project: Project) {
20 name = project.name
21 description = "Coroutines support libraries for Kotlin"
22 url = "https://github.com/Kotlin/kotlinx.coroutines"
23
24 licenses {
25 license {
26 name = "The Apache Software License, Version 2.0"
27 url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
28 distribution = "repo"
29 }
30 }
31
32 developers {
33 developer {
34 id = "JetBrains"
35 name = "JetBrains Team"
36 organization = "JetBrains"
37 organizationUrl = "https://www.jetbrains.com"
38 }
39 }
40
41 scm {
42 url = "https://github.com/Kotlin/kotlinx.coroutines"
43 }
44 }
45
46 /**
47 * 'libs.space.pub' is a dev option that is set on our CI in order to publish
48 * dev build into 'https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven' Maven repository.
49 * In order to use it, pass the corresponding ENV to the TC 'Deploy' task.
50 */
51 private val spacePublicationEnabled = System.getenv("libs.space.pub")?.equals("true") ?: false
52
mavenRepositoryUrinull53 fun mavenRepositoryUri(): URI {
54 if (spacePublicationEnabled) {
55 return URI("https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven")
56 }
57
58 val repositoryId: String? = System.getenv("libs.repository.id")
59 return if (repositoryId == null) {
60 URI("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
61 } else {
62 URI("https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId")
63 }
64 }
65
configureMavenPublicationnull66 fun configureMavenPublication(rh: RepositoryHandler, project: Project) {
67 rh.maven {
68 url = mavenRepositoryUri()
69 credentials {
70 if (spacePublicationEnabled) {
71 // Configure space credentials
72 username = project.getSensitiveProperty("libs.space.user")
73 password = project.getSensitiveProperty("libs.space.password")
74 } else {
75 // Configure sonatype credentials
76 username = project.getSensitiveProperty("libs.sonatype.user")
77 password = project.getSensitiveProperty("libs.sonatype.password")
78 }
79 }
80 }
81 }
82
signPublicationIfKeyPresentnull83 fun signPublicationIfKeyPresent(project: Project, publication: MavenPublication) {
84 val keyId = project.getSensitiveProperty("libs.sign.key.id")
85 val signingKey = project.getSensitiveProperty("libs.sign.key.private")
86 val signingKeyPassphrase = project.getSensitiveProperty("libs.sign.passphrase")
87 if (!signingKey.isNullOrBlank()) {
88 project.extensions.configure<SigningExtension>("signing") {
89 useInMemoryPgpKeys(keyId, signingKey, signingKeyPassphrase)
90 sign(publication)
91 }
92 }
93 }
94
getSensitivePropertynull95 private fun Project.getSensitiveProperty(name: String): String? {
96 return project.findProperty(name) as? String ?: System.getenv(name)
97 }
98
99 /**
100 * This unbelievable piece of engineering^W programming is a workaround for the following issues:
101 * - https://github.com/gradle/gradle/issues/26132
102 * - https://youtrack.jetbrains.com/issue/KT-61313/
103 *
104 * Long story short:
105 * 1) Single module produces multiple publications
106 * 2) 'Sign' plugin signs them
107 * 3) Signature files are re-used, which Gradle detects and whines about an implicit dependency
108 *
109 * There are three patterns that we workaround:
110 * 1) 'Sign' does not depend on 'publish'
111 * 2) Empty 'javadoc.jar.asc' got reused between publications (kind of a implication of the previous one)
112 * 3) `klib` signatures are reused where appropriate
113 *
114 * It addresses the following failures:
115 * ```
116 * Gradle detected a problem with the following location: 'kotlinx.coroutines/kotlinx-coroutines-core/build/classes/kotlin/macosArm64/main/klib/kotlinx-coroutines-core.klib.asc'.
117 * Reason: Task ':kotlinx-coroutines-core:linkWorkerTestDebugTestMacosArm64' uses this output of task ':kotlinx-coroutines-core:signMacosArm64Publication' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
118 *
119 * ```
120 * and
121 * ```
122 * Gradle detected a problem with the following location: 'kotlinx-coroutines-core/build/libs/kotlinx-coroutines-core-1.7.2-SNAPSHOT-javadoc.jar.asc'.
123 * Reason: Task ':kotlinx-coroutines-core:publishAndroidNativeArm32PublicationToMavenLocal' uses this output of task ':kotlinx-coroutines-core:signAndroidNativeArm64Publication' without declaring an explicit or implicit dependency.
124 * ```
125 */
Projectnull126 fun Project.establishSignDependencies() {
127 tasks.withType<Sign>().configureEach {
128 val pubName = name.removePrefix("sign").removeSuffix("Publication")
129 // Gradle#26132 -- establish dependency between sign and link tasks, as well as compile ones
130 mustRunAfter(tasks.matching { it.name == "linkDebugTest$pubName" })
131 mustRunAfter(tasks.matching { it.name == "linkWorkerTestDebugTest$pubName" })
132 mustRunAfter(tasks.matching { it.name == "compileTestKotlin$pubName" })
133 }
134
135 // Sign plugin issues and publication:
136 // Establish dependency between 'sign' and 'publish*' tasks
137 tasks.withType<AbstractPublishToMaven>().configureEach {
138 dependsOn(tasks.withType<Sign>())
139 }
140 }
141
142 /**
143 * Re-configure common publication to depend on JVM artifact only in pom.xml.
144 * It allows us to keep backwards compatibility with pre-multiplatform 'kotlinx-coroutines' publication scheme
145 * for Maven consumers:
146 * - Previously, we published 'kotlinx-coroutines-core' as the JVM artifact
147 * - With a multiplatform enabled as is, 'kotlinx-coroutines-core' is a common artifact not consumable from Maven,
148 * instead, users should depend on 'kotlinx-coroutines-core-jvm'
149 * - To keep the compatibility and experience, we do add dependency on 'kotlinx-coroutines-core-jvm' for
150 * 'kotlinx-coroutines-core' in pom.xml only (e.g. Gradle will keep using the metadata), so Maven users can
151 * depend on previous coordinates.
152 *
153 * Original code comment:
154 * Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module
155 * metadata can still get the platform artifact and transitive dependencies from the POM.
156 */
reconfigureMultiplatformPublicationnull157 public fun Project.reconfigureMultiplatformPublication(jvmPublication: MavenPublication) {
158 val mavenPublications =
159 extensions.getByType(PublishingExtension::class.java).publications.withType<MavenPublication>()
160 val kmpPublication = mavenPublications.getByName("kotlinMultiplatform")
161
162 var jvmPublicationXml: XmlProvider? = null
163 jvmPublication.pom.withXml { jvmPublicationXml = this }
164
165 kmpPublication.pom.withXml {
166 val root = asNode()
167 // Remove the original content and add the content from the platform POM:
168 root.children().toList().forEach { root.remove(it as Node) }
169 jvmPublicationXml!!.asNode().children().forEach { root.append(it as Node) }
170
171 // Adjust the self artifact ID, as it should match the root module's coordinates:
172 ((root["artifactId"] as NodeList).first() as Node).setValue(kmpPublication.artifactId)
173
174 // Set packaging to POM to indicate that there's no artifact:
175 root.appendNode("packaging", "pom")
176
177 // Remove the original platform dependencies and add a single dependency on the platform module:
178 val dependencies = (root["dependencies"] as NodeList).first() as Node
179 dependencies.children().toList().forEach { dependencies.remove(it as Node) }
180 dependencies.appendNode("dependency").apply {
181 appendNode("groupId", jvmPublication.groupId)
182 appendNode("artifactId", jvmPublication.artifactId)
183 appendNode("version", jvmPublication.version)
184 appendNode("scope", "compile")
185 }
186 }
187
188 // TODO verify if this is still relevant
189 tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication" }.configureEach {
190 @Suppress("DEPRECATION")
191 dependsOn(tasks["generatePomFileFor${jvmPublication.name.capitalize()}Publication"])
192 }
193 }
194
195 // Top-level deploy task that publishes all artifacts
registerTopLevelDeployTasknull196 public fun Project.registerTopLevelDeployTask() {
197 assert(this === rootProject)
198 tasks.register("deploy") {
199 allprojects {
200 val publishTasks = tasks.matching { it.name == "publish" }
201 dependsOn(publishTasks)
202 }
203 }
204 }
205
registerEmptyJavadocArtifactnull206 public fun Project.registerEmptyJavadocArtifact(): TaskProvider<Jar> {
207 return tasks.register("javadocJar", Jar::class) {
208 archiveClassifier = "javadoc"
209 // contents are deliberately left empty
210 }
211 }
212
213