@@ -4,17 +4,30 @@ import groovy.lang.*
4
4
import org.gradle.api.*
5
5
import org.gradle.api.plugins.*
6
6
import org.gradle.api.publish.*
7
+ import org.gradle.api.publish.maven.MavenPom
8
+ import org.gradle.api.publish.maven.MavenPublication
7
9
import org.gradle.api.publish.maven.plugins.*
8
10
import org.gradle.api.publish.maven.tasks.*
9
11
import org.gradle.api.publish.plugins.*
10
12
import org.gradle.api.tasks.*
13
+ import org.gradle.jvm.tasks.Jar
14
+ import org.gradle.plugins.signing.SigningExtension
15
+ import org.gradle.plugins.signing.SigningPlugin
11
16
import org.gradle.util.*
12
17
import java.net.*
13
18
import java.text.*
14
19
import java.util.*
15
20
16
21
@Suppress(" DEPRECATION" )
17
22
open class PublishingConfiguration {
23
+ var libraryRepoUrl: String? = null
24
+
25
+ val sonatype = SonatypeConfiguration ()
26
+ fun sonatype (configure : Action <SonatypeConfiguration >) {
27
+ configure.execute(sonatype)
28
+ sonatype.isSelected = true
29
+ }
30
+
18
31
@Deprecated(" Avoid publishing to bintray" )
19
32
val bintray = BintrayConfiguration ()
20
33
@Deprecated(" Avoid publishing to bintray" )
@@ -45,7 +58,11 @@ open class PublishingConfiguration {
45
58
}
46
59
}
47
60
48
- // TODO: Add sonatype configuration
61
+ class SonatypeConfiguration {
62
+ // no things to configure here for now
63
+ internal var isSelected: Boolean = false
64
+ }
65
+
49
66
// TODO: Add space configuration
50
67
51
68
// TODO: Remove all bintray-related configuration after migration
@@ -81,7 +98,7 @@ fun Project.configureProjectVersion() {
81
98
}
82
99
83
100
@Suppress(" DEPRECATION" )
84
- fun Project.configurePublishing (publishing : PublishingConfiguration ) {
101
+ internal fun Project.configurePublishing (publishing : PublishingConfiguration ) {
85
102
val ext = extensions.getByType(ExtraPropertiesExtension ::class .java)
86
103
87
104
val buildLocal = " buildLocal"
@@ -96,19 +113,34 @@ fun Project.configurePublishing(publishing: PublishingConfiguration) {
96
113
includeProjects.forEach { subproject ->
97
114
subproject.applyMavenPublish()
98
115
subproject.createBuildRepository(buildLocal, rootBuildLocal)
116
+ subproject.configurePublications(publishing)
99
117
}
100
118
101
- // If bintray is configured, create version task and configure subprojects
102
- val bintray = if (ext.get(" infra.release" ) == true )
103
- publishing.bintray
104
- else
105
- publishing.bintrayDev ? : publishing.bintray
106
-
107
- val enableBintray = verifyBintrayConfiguration(bintray)
108
- if (enableBintray) {
109
- createBintrayVersionTask(bintray)
110
- includeProjects.forEach { subproject ->
111
- subproject.createBintrayRepository(bintray)
119
+ createVersionPrepareTask()
120
+
121
+ if (publishing.sonatype.isSelected) {
122
+ if (verifySonatypeConfiguration()) {
123
+ if (ext.get(" infra.release" ) != true ) {
124
+ throw KotlinInfrastructureException (" Cannot publish development version to Sonatype." )
125
+ }
126
+ includeProjects.forEach { subproject ->
127
+ subproject.createSonatypeRepository()
128
+ subproject.configureSigning()
129
+ }
130
+ }
131
+ } else {
132
+ // If bintray is configured, create version task and configure subprojects
133
+ val bintray = if (ext.get(" infra.release" ) == true )
134
+ publishing.bintray
135
+ else
136
+ publishing.bintrayDev ? : publishing.bintray
137
+
138
+ val enableBintray = verifyBintrayConfiguration(bintray)
139
+ if (enableBintray) {
140
+ createBintrayVersionTask(bintray)
141
+ includeProjects.forEach { subproject ->
142
+ subproject.createBintrayRepository(bintray)
143
+ }
112
144
}
113
145
}
114
146
@@ -194,7 +226,7 @@ private fun Project.verifyBintrayConfiguration(bintray: BintrayConfiguration): B
194
226
return true
195
227
}
196
228
197
- fun Project.createBintrayRepository (bintray : BintrayConfiguration ) {
229
+ private fun Project.createBintrayRepository (bintray : BintrayConfiguration ) {
198
230
val username = bintray.username
199
231
? : throw KotlinInfrastructureException (" Cannot create version. User has not been specified." )
200
232
val password = bintray.password
@@ -222,8 +254,14 @@ private fun BintrayConfiguration.api(section: String): String {
222
254
return " https://api.bintray.com/$section /$organization /$repository /$library "
223
255
}
224
256
225
- fun Project.createBintrayVersionTask (bintray : BintrayConfiguration ) {
226
- task<DefaultTask >(" publishBintrayCreateVersion" ) {
257
+ private fun Project.createVersionPrepareTask (): TaskProvider <DefaultTask > {
258
+ return task<DefaultTask >(" publishPrepareVersion" ) {
259
+ group = PublishingPlugin .PUBLISH_TASK_GROUP
260
+ }
261
+ }
262
+
263
+ private fun Project.createBintrayVersionTask (bintray : BintrayConfiguration ) {
264
+ val bintrayCreateVersion = task<DefaultTask >(" publishBintrayCreateVersion" ) {
227
265
group = PublishingPlugin .PUBLISH_TASK_GROUP
228
266
doFirst {
229
267
val username = bintray.username
@@ -265,4 +303,131 @@ fun Project.createBintrayVersionTask(bintray: BintrayConfiguration) {
265
303
}
266
304
}
267
305
}
268
- }
306
+ tasks.named(" publishPrepareVersion" ).configure {
307
+ it.dependsOn(bintrayCreateVersion)
308
+ }
309
+ }
310
+
311
+
312
+ private fun Project.verifySonatypeConfiguration (): Boolean {
313
+ fun missing (what : String ): Boolean {
314
+ logger.warn(" INFRA: Sonatype publishing will not be possible due to missing $what ." )
315
+ return false
316
+ }
317
+
318
+ sonatypeUsername ? : return missing(" username" )
319
+ val password = sonatypePassword ? : return missing(" password" )
320
+ if (password.startsWith(" credentialsJSON" )) {
321
+ logger.warn(" INFRA: API key secure token was not expanded, publishing is not possible." )
322
+ return false
323
+ }
324
+
325
+ if (password.trim() != password) {
326
+ logger.warn(" INFRA: API key secure token was expanded to a value with whitespace around it." )
327
+ }
328
+
329
+ if (password.trim().isEmpty()) {
330
+ logger.warn(" INFRA: API key secure token was expanded to empty string." )
331
+ }
332
+ return true
333
+ }
334
+
335
+ private fun Project.createSonatypeRepository () {
336
+ val username = project.sonatypeUsername
337
+ ? : throw KotlinInfrastructureException (" Cannot setup publication. User has not been specified." )
338
+ val password = project.sonatypePassword
339
+ ? : throw KotlinInfrastructureException (" Cannot setup publication. Password (API key) has not been specified." )
340
+
341
+ extensions.configure(PublishingExtension ::class .java) { publishing ->
342
+ publishing.repositories.maven { repo ->
343
+ repo.name = " sonatype"
344
+ repo.url = sonatypeRepositoryUri()
345
+ repo.credentials { credentials ->
346
+ credentials.username = username
347
+ credentials.password = password.trim()
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ private fun Project.configurePublications (publishing : PublishingConfiguration ) {
354
+ if (publishing.libraryRepoUrl.isNullOrEmpty()) {
355
+ logger.warn(" INFRA: library source control repository URL is not set, publication won't be accepted by Sonatype." )
356
+ }
357
+ val javadocJar = tasks.create(" javadocJar" , Jar ::class .java).apply {
358
+ archiveClassifier.set(" javadoc" )
359
+ }
360
+ extensions.configure(PublishingExtension ::class .java) { publishingExtension ->
361
+ publishingExtension.publications.all {
362
+ with (it as MavenPublication ) {
363
+ artifact(javadocJar)
364
+ configureRequiredPomAttributes(project, publishing)
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ fun Project.mavenPublicationsPom (action : Action <MavenPom >) {
371
+ extensions.configure(PublishingExtension ::class .java) { publishingExtension ->
372
+ publishingExtension.publications.all {
373
+ action.execute((it as MavenPublication ).pom)
374
+ }
375
+ }
376
+ }
377
+
378
+ private fun MavenPublication.configureRequiredPomAttributes (project : Project , publishing : PublishingConfiguration ) {
379
+ val publication = this
380
+ // TODO: get rid of 'it's
381
+ pom {
382
+ it.name.set(publication.artifactId)
383
+ it.description.set(project.description ? : publication.artifactId)
384
+ it.url.set(publishing.libraryRepoUrl)
385
+ it.licenses {
386
+ it.license {
387
+ it.name.set(" The Apache License, Version 2.0" )
388
+ it.url.set(" http://www.apache.org/licenses/LICENSE-2.0.txt" )
389
+ }
390
+ }
391
+ it.scm {
392
+ it.url.set(publishing.libraryRepoUrl)
393
+ }
394
+ it.developers {
395
+ it.developer {
396
+ it.name.set(" JetBrains Team" )
397
+ it.organization.set(" JetBrains" )
398
+ it.organizationUrl.set(" https://www.jetbrains.com" )
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ private fun Project.configureSigning () {
405
+ project.pluginManager.apply (SigningPlugin ::class .java)
406
+ val keyId = project.propertyOrEnv(" libs.sign.key.id" )
407
+ val signingKey = project.propertyOrEnv(" libs.sign.key.private" )
408
+ val signingKeyPassphrase = project.propertyOrEnv(" libs.sign.passphrase" )
409
+
410
+ if (keyId != null ) {
411
+ project.extensions.configure<SigningExtension >(" signing" ) {
412
+ it.useInMemoryPgpKeys(keyId, signingKey, signingKeyPassphrase)
413
+ it.sign(extensions.getByType(PublishingExtension ::class .java).publications) // all publications
414
+ }
415
+ } else {
416
+ logger.warn(" INFRA: signing key id is not specified, artifact signing is not enabled." )
417
+ }
418
+ }
419
+
420
+
421
+ private fun Project.sonatypeRepositoryUri (): URI {
422
+ val repositoryId: String? = System .getenv(" libs.repository.id" )
423
+ return if (repositoryId == null ) {
424
+ // Using implicitly created staging, for MPP it's likely a mistake
425
+ logger.warn(" INFRA: using an implicitly created staging for ${project.rootProject.name} " )
426
+ URI (" https://oss.sonatype.org/service/local/staging/deploy/maven2/" )
427
+ } else {
428
+ URI (" https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId " )
429
+ }
430
+ }
431
+
432
+ private val Project .sonatypeUsername: String? get() = propertyOrEnv(" libs.sonatype.user" )
433
+ private val Project .sonatypePassword: String? get() = propertyOrEnv(" libs.sonatype.password" )
0 commit comments