2020-05-25 11:23:45 +02:00
/ *
* Copyright 2020 dorkbox , llc
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
package dorkbox.gradlePublish
import de.marcphilipp.gradle.nexus.NexusPublishExtension
import de.marcphilipp.gradle.nexus.NexusRepository
import de.marcphilipp.gradle.nexus.NexusRepositoryContainer
2020-06-08 22:20:22 +02:00
import io.codearte.gradle.nexus.CloseRepositoryTask
2020-05-25 11:23:45 +02:00
import io.codearte.gradle.nexus.NexusStagingExtension
2020-06-08 22:20:22 +02:00
import io.codearte.gradle.nexus.ReleaseRepositoryTask
2020-05-25 11:23:45 +02:00
import org.gradle.api.Action
import org.gradle.api.DomainObjectCollection
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
2020-08-17 16:16:23 +02:00
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.util.PatternFilterable
2020-05-25 11:23:45 +02:00
import org.gradle.jvm.tasks.Jar
2020-06-08 22:20:22 +02:00
import org.gradle.plugins.signing.SigningExtension
import org.gradle.plugins.signing.signatory.internal.pgp.InMemoryPgpSignatoryProvider
2020-08-17 16:16:23 +02:00
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
2020-06-08 22:20:22 +02:00
import java.io.File
import java.time.Duration
2020-06-02 23:06:50 +02:00
import java.util.*
2020-05-25 11:23:45 +02:00
/ * *
* For managing ( what should be common sense ) gradle tasks , such as :
* - publishing gradle projects to sonatype
* /
@Suppress ( " UnstableApiUsage " , " unused " )
class PublishPlugin : Plugin < Project > {
2020-06-02 22:44:56 +02:00
companion object {
init {
// To fix maven+gradle moronic incompatibilities: https://github.com/gradle/gradle/issues/11308
System . setProperty ( " org.gradle.internal.publish.checksums.insecure " , " true " )
}
}
2020-05-25 11:23:45 +02:00
private lateinit var project : Project
override fun apply ( project : Project ) {
this . project = project
// https://discuss.gradle.org/t/can-a-plugin-itself-add-buildscript-dependencies-and-then-apply-a-plugin/25039/4
apply ( " java " )
apply ( " maven-publish " )
apply ( " signing " )
apply ( " de.marcphilipp.nexus-publish " )
apply ( " io.codearte.nexus-staging " )
// Create the Plugin extension object (for users to configure publishing).
val config = project . extensions . create ( " publishToSonatype " , PublishToSonatype :: class . java , project )
// specific configuration later in after evaluate!!
nexusPublishing {
packageGroup . set ( project . provider { config . groupId } )
2020-06-08 22:20:22 +02:00
clientTimeout . set ( project . provider { config . httpTimeout } )
connectTimeout . set ( project . provider { config . httpTimeout } )
2020-05-25 11:23:45 +02:00
repositories ( Action < NexusRepositoryContainer > {
it . sonatype ( Action < NexusRepository > { repo ->
2020-06-08 22:20:22 +02:00
assignFromProp ( " sonatypeUserName " , config . sonatype . userName ) { repo . username . set ( project . provider { it } ) }
assignFromProp ( " sonatypePassword " , config . sonatype . password ) { repo . password . set ( project . provider { it } ) }
2020-05-25 11:23:45 +02:00
} )
} )
}
publishing {
publications { pub ->
val mavPub = pub . maybeCreate ( " maven " , MavenPublication :: class . java )
mavPub . from ( project . components . getByName ( " java " ) )
// create the pom
mavPub . pom { pom ->
pom . organization {
}
pom . issueManagement {
2020-06-08 22:20:22 +02:00
val sign = project . extensions . getByName ( " signing " ) as SigningExtension
// check what the signatory is. if it's InMemoryPgpSignatoryProvider, then we ALREADY configured it!
if ( sign . signatory !is InMemoryPgpSignatoryProvider ) {
// we haven't configured it yet AND we don't know which value is set first!
// setup the sonatype PRIVATE KEY information
assignFromProp ( " sonatypePrivateKeyFile " , " " ) {
project . extensions . extraProperties [ " sonatypePrivateKeyFile " ] = it
if ( project . extensions . extraProperties . has ( " sonatypePrivateKeyPassword " ) ) {
sign . apply {
useInMemoryPgpKeys ( File ( it ) . readText ( ) , project . extensions . extraProperties [ " sonatypePrivateKeyPassword " ] as String )
}
}
}
assignFromProp ( " sonatypePrivateKeyPassword " , " " ) {
project . extensions . extraProperties [ " sonatypePrivateKeyPassword " ] = it
if ( project . extensions . extraProperties . has ( " sonatypePrivateKeyFile " ) ) {
sign . apply {
useInMemoryPgpKeys ( File ( project . extensions . extraProperties [ " sonatypePrivateKeyFile " ] as String ) . readText ( ) , it )
}
}
}
}
2020-05-25 11:23:45 +02:00
}
pom . scm {
}
pom . developers {
}
}
mavPub . artifact ( project . tasks . create ( " sourceJar " , Jar :: class . java ) . apply {
description = " Creates a JAR that contains the source code. "
archiveClassifier . set ( " sources " )
} )
mavPub . artifact ( project . tasks . create ( " javaDocJar " , Jar :: class . java ) . apply {
description = " Creates a JAR that contains the javadocs. "
2020-08-17 16:16:23 +02:00
// nothing special about javadocs. sources is all we care about
2020-05-25 11:23:45 +02:00
archiveClassifier . set ( " javadoc " )
} )
}
}
project . tasks . getByName ( " closeAndReleaseRepository " ) . mustRunAfter ( project . tasks . getByName ( " publishToSonatype " ) )
project . tasks . create ( " publishToSonatypeAndRelease " , PublishAndReleaseProjectTask :: class . java ) . apply {
2020-06-02 22:44:56 +02:00
group = " publish and release "
2020-05-25 11:23:45 +02:00
2021-01-15 01:13:46 +01:00
dependsOn ( " publishToMavenLocal " , " publishToSonatype " , " closeAndReleaseRepository " )
2020-05-25 11:23:45 +02:00
}
2020-06-02 22:44:56 +02:00
project . tasks . getByName ( " publishToMavenLocal " ) . apply {
group = " publish and release "
}
2020-05-25 11:23:45 +02:00
project . tasks . withType < PublishToMavenLocal > {
2020-08-08 00:10:43 +02:00
doFirst {
println ( " \t Publishing ' ${publication.groupId} : ${publication.artifactId} : ${publication.version} ' to Maven Local " )
}
2020-05-25 11:23:45 +02:00
onlyIf {
val pub = get ( )
publication == pub . publications . getByName ( " maven " )
}
}
project . tasks . withType < PublishToMavenRepository > {
doFirst {
2020-08-08 01:00:04 +02:00
val url = " https://oss.sonatype.org/content/repositories/releases/ "
val projectName = config . groupId . replace ( '.' , '/' )
// output the release URL in the console
println ( " \t Publishing ' ${publication.groupId} : ${publication.artifactId} : ${publication.version} ' to $url $projectName / ${config.name} / ${config.version} / " )
2020-05-25 11:23:45 +02:00
}
onlyIf {
val pub = get ( )
publication == pub . publications . getByName ( " maven " ) &&
repository == pub . repositories . getByName ( " sonatype " )
}
}
// have to get the configuration extension data
// required to make sure the tasks run in the correct order
project . afterEvaluate {
nexusStaging {
2020-06-08 22:20:22 +02:00
assignFromProp ( " sonatypeUserName " , config . sonatype . userName ) { username = it }
assignFromProp ( " sonatypePassword " , config . sonatype . password ) { password = it }
}
val closeTask = project . tasks . findByName ( " closeRepository " ) as CloseRepositoryTask
closeTask . apply {
delayBetweenRetriesInMillis = config . retryDelay . toMillis ( ) . toInt ( )
numberOfRetries = config . retryLimit
}
val releaseTask = project . tasks . findByName ( " releaseRepository " ) as ReleaseRepositoryTask
releaseTask . apply {
delayBetweenRetriesInMillis = config . retryDelay . toMillis ( ) . toInt ( )
numberOfRetries = config . retryLimit
}
// create the sign task to sign the artifact jars before uploading
val sign = project . extensions . getByName ( " signing " ) as SigningExtension
sign . apply {
sign ( ( project . extensions . getByName ( " publishing " ) as PublishingExtension ) . publications . getByName ( " maven " ) )
}
2020-08-17 16:16:23 +02:00
// fix the maven source jar
val sourceJarTask = project . tasks . findByName ( " sourceJar " ) as Jar
sourceJarTask . apply {
val sourceSets = project . extensions . getByName ( " sourceSets " ) as org . gradle . api . tasks . SourceSetContainer
val mainSourceSet : SourceSet = sourceSets . getByName ( " main " )
// want to included java + kotlin for the sources
// kotlin stuff. Sometimes kotlin depends on java files, so the kotlin sourcesets have BOTH java + kotlin.
// we want to make sure to NOT have both, as it will screw up creating the jar!
2020-08-18 21:24:36 +02:00
try {
val kotlin = ( mainSourceSet as org . gradle . api . internal . HasConvention )
. convention
. getPlugin ( KotlinSourceSet :: class . java )
. kotlin
val srcDirs = kotlin . srcDirs
val kotlinFiles = kotlin . asFileTree . matching { it : PatternFilterable ->
// find out if this file (usually, just a java file) is ALSO in the java sourceset.
// this is to prevent DUPLICATES in the jar, because sometimes kotlin must be .kt + .java in order to compile!
val javaFiles = mainSourceSet . java . files . map { file ->
// by definition, it MUST be one of these
val base = srcDirs . first {
// find out WHICH src dir base path it is
val path = project . buildDir . relativeTo ( it )
path . path . isNotEmpty ( )
}
file . relativeTo ( base ) . path
2020-08-17 16:16:23 +02:00
}
2020-08-18 21:24:36 +02:00
it . setExcludes ( javaFiles )
2020-08-17 16:16:23 +02:00
}
2020-08-18 21:24:36 +02:00
from ( kotlinFiles )
} catch ( ignored : Exception ) {
// maybe we don't have kotlin for the project
2020-08-17 16:16:23 +02:00
}
2020-08-23 23:36:11 +02:00
// java stuff (it is compiled AFTER kotlin), and it is ALREADY included!
2020-08-17 16:16:23 +02:00
// kotlin is always compiled first
2020-08-23 23:36:11 +02:00
// from(mainSourceSet.java)
2020-08-17 16:16:23 +02:00
}
2020-06-08 22:20:22 +02:00
// output how much the time-outs are
val durationString = config . httpTimeout . toString ( ) . substring ( 2 )
. replace ( " ( \\ d[HMS])(?! $ ) " , " $ 1 " ) . toLowerCase ( )
val fullReleaseTimeout = Duration . ofMillis ( config . retryDelay . toMillis ( ) * config . retryLimit )
val fullReleaseString = fullReleaseTimeout . toString ( ) . substring ( 2 )
. replace ( " ( \\ d[HMS])(?! $ ) " , " $ 1 " ) . toLowerCase ( )
2020-08-08 00:10:43 +02:00
project . tasks . findByName ( " publishToSonatype " ) ?. doFirst {
2020-08-08 00:52:04 +02:00
println ( " \t Publishing to Sonatype: ${config.groupId} : ${config.artifactId} : ${config.version} " )
2020-08-08 00:10:43 +02:00
println ( " \t \t Sonatype HTTP timeout: $durationString " )
println ( " \t \t Sonatype API timeout: $fullReleaseString " )
}
2020-05-25 11:23:45 +02:00
}
project . childProjects . values . forEach {
it . pluginManager . apply ( PublishPlugin :: class . java )
}
}
// required to make sure the plugins are correctly applied. ONLY applying it to the project WILL NOT work.
// The plugin must also be applied to the root project
private fun apply ( id : String ) {
if ( project . rootProject . pluginManager . findPlugin ( id ) == null ) {
project . rootProject . pluginManager . apply ( id )
}
if ( project . pluginManager . findPlugin ( id ) == null ) {
project . pluginManager . apply ( id )
}
}
private inline fun < reified S : Any > DomainObjectCollection < in S > . withType ( noinline configuration : S . ( ) -> Unit ) =
withType ( S :: class . java , configuration )
@Suppress ( " UNCHECKED_CAST " )
private fun get ( ) : PublishingExtension {
return project . extensions . getByName ( " publishing " ) as PublishingExtension
}
private fun publishing ( configure : PublishingExtension . ( ) -> Unit ) : Unit =
project . extensions . configure ( " publishing " , configure )
private fun nexusStaging ( configure : NexusStagingExtension . ( ) -> Unit ) : Unit =
project . extensions . configure ( " nexusStaging " , configure )
private fun nexusPublishing ( configure : NexusPublishExtension . ( ) -> Unit ) : Unit =
project . extensions . configure ( " nexusPublishing " , configure )
2020-06-08 22:20:22 +02:00
2020-08-17 16:16:23 +02:00
@Suppress ( " UNCHECKED_CAST " , " RemoveExplicitTypeArguments " )
2020-06-08 22:20:22 +02:00
private fun assignFromProp ( propertyName : String , defaultValue : String , apply : ( value : String ) -> Unit ) {
// THREE possibilities for property registration or assignment
// 1) we have MANUALLY defined this property (via the configuration object)
// 1) gradleUtil properties loaded first
// -> gradleUtil's adds a function that everyone else (plugin/task) can call to get values from properties
// 2) gradleUtil properties loaded last
// -> others add a function that gradleUtil's call to set values from properties
// 1
if ( defaultValue . isNotEmpty ( ) ) {
// println("ASSIGN DEFAULT: $defaultValue")
apply ( defaultValue )
return
}
// 2
if ( project . extensions . extraProperties . has ( propertyName ) ) {
// println("ASSIGN PROP FROM FILE: $propertyName")
apply ( project . extensions . extraProperties [ propertyName ] as String )
return
}
// 3
2020-08-17 16:16:23 +02:00
val loaderFunctions : ArrayList < Plugin < Pair < String , String > > > ?
2020-06-08 22:20:22 +02:00
if ( project . extensions . extraProperties . has ( " property_loader_functions " ) ) {
loaderFunctions = project . extensions . extraProperties [ " property_loader_functions " ] as ArrayList < Plugin < Pair < String , String > > > ?
} else {
loaderFunctions = ArrayList < Plugin < Pair < String , String > > > ( )
project . extensions . extraProperties [ " property_loader_functions " ] = loaderFunctions
}
// println("ADD LOADER FUNCTION: $propertyName")
loaderFunctions !! . add ( Plugin < Pair < String , String > > ( ) {
if ( it . first == propertyName ) {
// println("EXECUTE LOADER FUNCTION: $propertyName")
apply ( it . second )
}
} )
}
2020-05-25 11:23:45 +02:00
}