Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import org.gradle.api.Project
* Ordering said hooks is a non-trivial operation and the result is usually quite fragile.
* Thus, we choose to do this small piece of configuration manually.
*/
@Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names
internal object CloudArtifactRegistry {

private const val spineRepoLocation = "https://europe-maven.pkg.dev/spine-event-engine"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,25 @@ import org.gradle.api.publish.maven.MavenPublication
* the [standard][org.gradle.api.publish.maven.MavenPublication] publication, and custom ones.
* To have both standard and custom publications, please specify custom artifact IDs or
* classifiers for each custom publication.
*
* @see StandardJavaPublicationHandler
*/
internal class CustomPublicationHandler(project: Project, destinations: Set<Repository>) :
PublicationHandler(project, destinations) {
internal class CustomPublicationHandler private constructor(
project: Project,
destinations: Set<Repository>
) : PublicationHandler(project, destinations) {

override fun handlePublications() {
project.publications.forEach {
(it as MavenPublication).copyProjectAttributes()
}
}

companion object : HandlerFactory<CustomPublicationHandler>() {
override fun create(
project: Project,
destinations: Set<Repository>,
vararg params: Any
): CustomPublicationHandler = CustomPublicationHandler(project, destinations)
}
}
127 changes: 104 additions & 23 deletions buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,43 +42,64 @@ private const val MAVEN_PUBLISH = "maven-publish"
/**
* Abstract base for handlers of publications in a project
* with [spinePublishing] settings declared.
*
* @param project The project to which the handler is applied.
* @param destinations The repositories for publishing artifacts of this project.
* In a multi-module project the destinations can be re-defined by
* specifying custom values in
* the [`spinePublishing`][io.spine.gradle.publish.SpinePublishing.destinations]
* extension applied to the subproject.
*/
internal sealed class PublicationHandler(
protected val project: Project,
private val destinations: Set<Repository>
protected var destinations: Set<Repository>
) {
/**
* Tells if this publication handler already [applied][apply].
*
* This safeguard is needed because we call the [apply] method
* under [Project.afterEvaluate] block, which could be called more than once.
* This flag helps to ensure that we call the [doApply] only once, thus
* avoiding repeated calls of the publication settings code.
* Remembers if the [apply] function was called by this handler.
*/
private var applied = false
private var applied: Boolean = false

fun apply() {
synchronized(this) {
if (applied) {
return@synchronized
}
doApply()
this.applied = true
/**
* Overwrites the [destinations] property with the given set.
*/
fun publishTo(alternativeDestinations: Set<Repository>) {
if (alternativeDestinations.isEmpty()) {
project.logger.info(
"The project ${project.path} is not going to be published because" +
" the publication handler `${this@PublicationHandler}`" +
" got an empty set of new `destinations`."
)
}
destinations = alternativeDestinations
}

/**
* Configures the publication of the associated [project].
*/
private fun doApply() {
project.run {
if (!hasCustomPublishing) {
apply(plugin = MAVEN_PUBLISH)
fun apply() {
synchronized(project) {
if (applied) {
return
}
pluginManager.withPlugin(MAVEN_PUBLISH) {
handlePublications()
registerDestinations()
configurePublishTask(destinations)
project.run {
// We apply the `maven-publish` plugin for modules with standard
// publishing automatically because they don't need custom DSL
// in their `build.gradle.kts` files.
// All the job is done by the `SpinePublishing` extension and
// `StandardPublicationHandler` instance associated with this project.
if (!hasCustomPublishing) {
apply(plugin = MAVEN_PUBLISH)
}
// And we do not apply the plugin for modules with custom publishing
// because they will need the `maven-publish` DSL to tune the publishing.
// Therefore, we only arrange the execution of our code when the plugin
// is applied.
pluginManager.withPlugin(MAVEN_PUBLISH) {
handlePublications()
registerDestinations()
configurePublishTask(destinations)
applied = true
}
}
}
}
Expand Down Expand Up @@ -131,6 +152,66 @@ internal sealed class PublicationHandler(
}
}
}

/**
* The abstract base for factories producing instances of classes
* derived from [io.spine.gradle.publish.PublicationHandler].
*
* The factory maintains associations between a path of the project to
* its publication handler.
*
* If the handler already exists, its settings are updated when
* the [serving] factory method is called.
*
* Otherwise, a new handler is created and associated with the project.
*
* @param H The type of the publication handlers produced by this repository.
* @see serving
*/
abstract class HandlerFactory<H : PublicationHandler> {

/**
* Maps a project path to the associated handler, if available.
*/
private val handlers = mutableMapOf<String, H>()

/**
* Obtains an instance of [PublicationHandler] for the given project.
*
* If the handler for the given [project] was already created, the handler
* gets new [destinations], [overwriting][publishTo] previously specified.
*
* @return the handler for the given project which would handle publishing to
* the specified [destinations].
*/
fun serving(project: Project, destinations: Set<Repository>, vararg params: Any): H {
synchronized(handlers) {
val path = project.path
var handler = handlers[path]
if (handler == null) {
handler = create(project, destinations, *params)
handlers[path] = handler
} else {
handler.publishTo(destinations)
}
return handler
}
}

/**
* Creates a new publication handler for the given project.
*
* @param project The project to which the handler applies.
* @param destinations The repositories for publishing artifacts of this project.
* @param params Optional parameters to be passed as constructor parameters for
* classes of the type [H].
*/
protected abstract fun create(
project: Project,
destinations: Set<Repository>,
vararg params: Any
): H
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ internal val Project.publishingExtension: PublishingExtension
internal val Project.publications: PublicationContainer
get() = publishingExtension.publications

/**
* Obtains an instance, if available, of [SpinePublishing] extension
* applied to this project.
*/
internal val Project.localSpinePublishing: SpinePublishing?
get() = extensions.findByType<SpinePublishing>()

/**
* Obtains [SpinePublishing] extension from this [Project].
*
Expand All @@ -66,7 +73,7 @@ internal val Project.publications: PublicationContainer
*/
internal val Project.spinePublishing: SpinePublishing
get() {
val local = this.extensions.findByType<SpinePublishing>()
val local = localSpinePublishing
if (local != null) {
return local
}
Expand Down
58 changes: 46 additions & 12 deletions buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ import org.gradle.kotlin.dsl.findByType
* and can be disabled via [SpinePublishing.protoJar].
*
* 3. [javadocJar] — Javadoc, generated upon Java sources from the `main` source set.
* If Javadoc for Kotlin is also needed, apply the Dokka plugin. It tunes `javadoc` task to generate
* docs upon Kotlin sources as well.
* If Javadoc for Kotlin is also needed, apply the Dokka plugin.
* It tunes the `javadoc` task to generate docs upon Kotlin sources as well.
*
* 4. [dokkaKotlinJar] — documentation generated by Dokka for Kotlin and Java sources
* using the Kotlin API mode.
Expand All @@ -147,6 +147,7 @@ import org.gradle.kotlin.dsl.findByType
* of the `test` source set. Use [SpinePublishing.testJar] to enable its publishing.
*
* @see [artifacts]
* @see SpinePublishing
*/
fun Project.spinePublishing(block: SpinePublishing.() -> Unit) {
apply<MavenPublishPlugin>()
Expand All @@ -161,7 +162,16 @@ fun Project.spinePublishing(block: SpinePublishing.() -> Unit) {
}

/**
* A Gradle extension for setting up publishing of spine modules using `maven-publish` plugin.
* A Gradle extension for setting up publishing of modules of Spine SDK modules
* using `maven-publish` plugin.
*
* ### Implementation Note
*
* This extension is overloaded with responsibilities.
* It basically does what an extension AND a Gradle plugin would normally do.
*
* We [should introduce a plugin class](https://github.com/SpineEventEngine/config/issues/562)
* and move the code related to creating tasks or setting dependencies between them into the plugin.
*
* @param project The project in which the extension is opened. By default, this project will be
* published as long as a [set][modules] of modules to publish is not specified explicitly.
Expand Down Expand Up @@ -240,9 +250,10 @@ open class SpinePublishing(private val project: Project) {
* )}
* ```
*
* Empty by default.
* If the property is not initialized, the destinations will be taken from
* the parent project.
*/
var destinations: Set<Repository> = emptySet()
lateinit var destinations: Set<Repository>

/**
* A prefix to be added before the name of each artifact.
Expand Down Expand Up @@ -318,8 +329,8 @@ open class SpinePublishing(private val project: Project) {
* }
* ```
*
* The resulting artifact is available under "test" classifier. For example,
* in Gradle 7+, one could depend on it like this:
* The resulting artifact is available under the "test" classifier.
* For example, in Gradle 7+, one could depend on it like this:
*
* ```
* implementation("io.spine:spine-client:$version@test")
Expand Down Expand Up @@ -419,16 +430,36 @@ open class SpinePublishing(private val project: Project) {
*/
private fun Project.setUpPublishing(jarFlags: JarFlags) {
val customPublishing = modulesWithCustomPublishing.contains(name) || customPublishing
val destinations = project.publishTo()
val handler = if (customPublishing) {
CustomPublicationHandler(project, destinations)
CustomPublicationHandler.serving(project, destinations)
} else {
StandardJavaPublicationHandler(project, jarFlags, destinations)
StandardJavaPublicationHandler.serving(project, destinations, jarFlags)
}
afterEvaluate {
handler.apply()
}
}

/**
* Obtains the set of repositories for publishing.
*
* If there is a local instance of [io.spine.gradle.publish.SpinePublishing] extension,
* the [destinations] are obtained from this instance.
* Otherwise, the function attempts to obtain it from a [parent project][Project.getParent].
* If there is no a parent project, an empty set is returned.
*
* The normal execution should end up at the root project of a multi-module project
* if there are no custom destinations specified by the local extension.
*/
private fun Project.publishTo(): Set<Repository> {
val ext = localSpinePublishing
if (ext != null && ext::destinations.isInitialized) {
return destinations
}
return parent?.publishTo() ?: emptySet()
}

/**
* Obtains an artifact ID for the given project.
*
Expand All @@ -447,8 +478,11 @@ open class SpinePublishing(private val project: Project) {
private fun ensureProtoJarExclusionsArePublished() {
val nonPublishedExclusions = protoJar.exclusions.minus(modules)
if (nonPublishedExclusions.isNotEmpty()) {
throw IllegalStateException("One or more modules are marked as `excluded from proto " +
"JAR publication`, but they are not even published: $nonPublishedExclusions")
error(
"One or more modules are marked as" +
" `excluded from proto JAR publication`," +
" but they are not even published: $nonPublishedExclusions."
)
}
}

Expand All @@ -472,7 +506,7 @@ open class SpinePublishing(private val project: Project) {
/**
* Ensures that publishing of a module is configured only from a single place.
*
* We allow configuration of publishing from two places - a root project and module itself.
* We allow configuration of publishing from two places - a root project and the module itself.
* Here we verify that publishing of a module is not configured in both places simultaneously.
*/
private fun ensureModulesNotDuplicated() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,53 @@ import org.gradle.kotlin.dsl.create
* A publication has a name and consists of one or more artifacts plus information about
* those artifacts – the metadata.
*
* An instance of this class represents [org.gradle.api.publish.maven.MavenPublication] named "mavenJava". It is generally
* accepted that a publication with this name contains a Java project published to one or
* more Maven repositories.
* An instance of this class represents
* [MavenPublication][org.gradle.api.publish.maven.MavenPublication]
* named [`"mavenJava"`][PUBLICATION_NAME].
* It is generally accepted that a publication with this name contains a Java project
* published to one or more Maven repositories.
*
* By default, only a jar with the compilation output of `main` source set and its
* metadata files are published. Other artifacts are specified through the
* [constructor parameter][jarFlags]. Please, take a look on [specifyArtifacts] for additional info.
* [constructor parameter][jarFlags].
* Please take a look on [specifyArtifacts] for additional info.
*
* @param jarFlags The flags for additional JARs published along with the compilation output.
* @param destinations Maven repositories to which the produced artifacts will be sent.
* @see <a href="https://docs.gradle.org/current/userguide/publishing_maven.html#publishing_maven:publications">
* The Maven Publish Plugin | Publications</a>
* @see CustomPublicationHandler
*/
internal class StandardJavaPublicationHandler(
internal class StandardJavaPublicationHandler private constructor(
project: Project,
private val jarFlags: JarFlags,
destinations: Set<Repository>,
) : PublicationHandler(project, destinations) {

companion object : HandlerFactory<StandardJavaPublicationHandler>() {

/**
* The name of the publication created by [StandardJavaPublicationHandler].
*/
const val PUBLICATION_NAME = "mavenJava"

override fun create(
project: Project,
destinations: Set<Repository>,
vararg params: Any
): StandardJavaPublicationHandler {
return StandardJavaPublicationHandler(project, params[0] as JarFlags, destinations)
}
}

/**
* Creates a new `"mavenJava"` [MavenPublication][org.gradle.api.publish.maven.MavenPublication]
* in the [project] associated with this publication handler.
*/
override fun handlePublications() {
val jars = project.artifacts(jarFlags)
val publications = project.publications
publications.create<MavenPublication>("mavenJava") {
publications.create<MavenPublication>(PUBLICATION_NAME) {
copyProjectAttributes()
specifyArtifacts(jars)
}
Expand Down
Loading
Loading