• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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