From 0263f7f95ed55175a022c43af3a89d4108f0dcbf Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Tue, 16 Jun 2026 17:15:36 +0300 Subject: [PATCH 1/3] build: extract publishing config into a build-logic convention plugin Every publishable module repeated the same ~45-line maven-publish + signing + POM block, so any change to the Sonatype coordinates, license, SCM, or signing setup meant editing nine build scripts in lockstep. Introduce a `build-logic` included build with a single precompiled script plugin, `dexpace.published-module`, that owns the publication, POM metadata, staging repository, coordinates (group `org.dexpace`, version `0.0.1-alpha.1`), and the CI-gated in-memory signing configuration. Each of the nine modules now applies `id("dexpace.published-module")` and keeps only its module-specific dependencies and toolchain overrides; the POM name and description continue to derive from `project.name`, so no module needs any further publishing configuration. A module that should not be published simply does not apply the plugin. The generated POMs, Gradle module metadata, coordinates, artifacts, and signing behaviour are unchanged: the per-module POMs are byte-identical to those produced before the change, and `publishToMavenLocal` continues to skip signing when no PGP key is present (signing is required only under CI). --- build-logic/build.gradle.kts | 20 +++++ build-logic/settings.gradle.kts | 18 +++++ .../dexpace.published-module.gradle.kts | 76 +++++++++++++++++++ sdk-async-coroutines/build.gradle.kts | 55 +------------- sdk-async-netty/build.gradle.kts | 55 +------------- sdk-async-reactor/build.gradle.kts | 55 +------------- sdk-async-virtualthreads/build.gradle.kts | 55 +------------- sdk-core/build.gradle.kts | 56 +------------- sdk-io-okio3/build.gradle.kts | 55 +------------- sdk-serde-jackson/build.gradle.kts | 55 +------------- sdk-transport-jdkhttp/build.gradle.kts | 55 +------------- sdk-transport-okhttp/build.gradle.kts | 55 +------------- settings.gradle.kts | 7 ++ 13 files changed, 148 insertions(+), 469 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/dexpace.published-module.gradle.kts diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000..a7de29e7 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 dexpace and Omar Aljarrah + * + * Licensed under the MIT License. See LICENSE in the project root. + * SPDX-License-Identifier: MIT + */ + +// The `kotlin-dsl` plugin lets this build compile precompiled script plugins — every +// `src/main/kotlin/*.gradle.kts` file becomes a plugin whose id is its file name minus the +// `.gradle.kts` suffix (e.g. `dexpace.published-module`). Consumers in the main build apply +// it by that id once `settings.gradle.kts` has `includeBuild("build-logic")` on the plugin +// classpath. +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..a1e6bf7b --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 dexpace and Omar Aljarrah + * + * Licensed under the MIT License. See LICENSE in the project root. + * SPDX-License-Identifier: MIT + */ + +// Standalone settings for the `build-logic` included build. This build compiles the +// repository's convention plugins (precompiled `*.gradle.kts` script plugins) so that the +// production modules can apply them by id instead of duplicating configuration. +// +// `build-logic` deliberately depends on nothing from the version catalog: its sole convention +// plugin wires the core `maven-publish` and `signing` plugins, which ship with Gradle itself +// and therefore need no version. Keeping the included build catalog-free avoids extra +// `dependencyResolutionManagement { versionCatalogs { ... } }` plumbing and the associated +// classpath coupling between the main build and its own build logic. + +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts new file mode 100644 index 00000000..c9cdf5b6 --- /dev/null +++ b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 dexpace and Omar Aljarrah + * + * Licensed under the MIT License. See LICENSE in the project root. + * SPDX-License-Identifier: MIT + */ + +// Convention plugin for every module that is published to Maven Central. It carries the +// `maven-publish` + `signing` setup, the shared POM metadata, the staging repository, and the +// CI-gated signing configuration that was previously copied verbatim into all nine module +// build scripts. +// +// A module opts in with `plugins { id("dexpace.published-module") }`. The publication name, +// coordinates, POM, repository, and signing behaviour are then identical across modules; the +// `name`/`description` fields derive from `project.name`, so a module needs no further +// publishing configuration. A module that must NOT be published simply does not apply this +// plugin. + +plugins { + `maven-publish` + signing +} + +// Coordinates. The group and version are the same for every published module and match the +// values declared on the root project; keeping the single literal here makes a coordinate +// change a one-file edit instead of a nine-file edit. +group = "org.dexpace" +version = "0.0.1-alpha.1" + +publishing { + publications { + create("library") { + from(components["java"]) + pom { + name.set(project.name) + description.set("Dexpace Java SDK — ${project.name}") + url.set("https://github.com/dexpace/java-sdk") + licenses { + license { + name.set("MIT License") + url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") + distribution.set("repo") + } + } + developers { + developer { + id.set("dexpace") + name.set("Dexpace SDK Team") + } + } + scm { + connection.set("scm:git:https://github.com/dexpace/java-sdk.git") + developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") + url.set("https://github.com/dexpace/java-sdk") + } + } + } + } + repositories { + // Local staging repository. CI must override this to publish to a real remote. + maven { + name = "local" + url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) + } + } +} + +signing { + isRequired = (System.getenv("CI") == "true") + val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") + val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") + if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + sign(publishing.publications["library"]) +} diff --git a/sdk-async-coroutines/build.gradle.kts b/sdk-async-coroutines/build.gradle.kts index b7c1a41b..c09591f9 100644 --- a/sdk-async-coroutines/build.gradle.kts +++ b/sdk-async-coroutines/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // Java 8 bytecode is inherited from the root build script — the module ships to JDK 8 consumers // just like `sdk-core` does. @@ -40,50 +38,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-async-netty/build.gradle.kts b/sdk-async-netty/build.gradle.kts index 86e1aa45..9db1ca97 100644 --- a/sdk-async-netty/build.gradle.kts +++ b/sdk-async-netty/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - dependencies { implementation(project(":sdk-core")) // `netty-common` carries `io.netty.util.concurrent.Future`/`Promise`/`EventExecutor` — @@ -30,50 +28,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-async-reactor/build.gradle.kts b/sdk-async-reactor/build.gradle.kts index e5425b83..671019c1 100644 --- a/sdk-async-reactor/build.gradle.kts +++ b/sdk-async-reactor/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - dependencies { implementation(project(":sdk-core")) // Reactor itself is Java 8 compatible and ships with `Mono.fromFuture(...)` / `Mono.toFuture()` @@ -33,50 +31,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-async-virtualthreads/build.gradle.kts b/sdk-async-virtualthreads/build.gradle.kts index 5813c12c..9c0603d4 100644 --- a/sdk-async-virtualthreads/build.gradle.kts +++ b/sdk-async-virtualthreads/build.gradle.kts @@ -11,13 +11,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // Virtual threads require JDK 21+. The root build script applies Java 8 to every Kotlin // module; we override here. The output bytecode targets Java 21 — consumers MUST be on // JDK 21 or newer to depend on this module. Both Kotlin and Java compile tasks must agree @@ -63,53 +61,6 @@ tasks.test { useJUnitPlatform() } -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} - // Detekt analysis is disabled on this module because detekt 1.23.x (incl. 1.23.6 and 1.23.8) // embeds a Kotlin compiler whose `org.jetbrains.kotlin.com.intellij.util.lang.JavaVersion.parse` // throws `IllegalArgumentException: 25.0.2` when running on JDK 25+ — the system JDK on this diff --git a/sdk-core/build.gradle.kts b/sdk-core/build.gradle.kts index 8c79e655..e837484c 100644 --- a/sdk-core/build.gradle.kts +++ b/sdk-core/build.gradle.kts @@ -9,13 +9,11 @@ plugins { kotlin("jvm") `java-test-fixtures` id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // repositories and shared compileOnly/implementation deps come from the root build.gradle.kts. dependencies { @@ -40,51 +38,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - // Local staging repository. CI must override this to publish to a real remote. - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-io-okio3/build.gradle.kts b/sdk-io-okio3/build.gradle.kts index 8e33c6b7..957a1ab4 100644 --- a/sdk-io-okio3/build.gradle.kts +++ b/sdk-io-okio3/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // repositories come from the root build.gradle.kts. // jvmToolchain(8) is inherited from the root via plugins.withId("org.jetbrains.kotlin.jvm"). // We intentionally do NOT override it here: a higher toolchain would let our code reference @@ -32,50 +30,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-serde-jackson/build.gradle.kts b/sdk-serde-jackson/build.gradle.kts index 2974fffc..7c426e7c 100644 --- a/sdk-serde-jackson/build.gradle.kts +++ b/sdk-serde-jackson/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // Java 8 bytecode is inherited from the root build script — jvmToolchain(8) and // jvmTarget=1.8 apply via plugins.withId("org.jetbrains.kotlin.jvm"). Jackson 2.18.x runs // on JDK 8+, so no toolchain override is required. @@ -33,50 +31,3 @@ dependencies { tasks.test { useJUnitPlatform() } - -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} diff --git a/sdk-transport-jdkhttp/build.gradle.kts b/sdk-transport-jdkhttp/build.gradle.kts index f57173e7..f632e693 100644 --- a/sdk-transport-jdkhttp/build.gradle.kts +++ b/sdk-transport-jdkhttp/build.gradle.kts @@ -11,13 +11,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // `java.net.http.HttpClient` was finalised in JEP 321 / Java 11. The root build script // applies Java 8 to every Kotlin module; we override here. The output bytecode targets // Java 11 — consumers MUST be on JDK 11 or newer to depend on this module. Both Kotlin @@ -64,53 +62,6 @@ tasks.test { useJUnitPlatform() } -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} - // Detekt analysis is disabled on this module because detekt 1.23.x (incl. 1.23.6 and 1.23.8) // embeds a Kotlin compiler whose `org.jetbrains.kotlin.com.intellij.util.lang.JavaVersion.parse` // throws `IllegalArgumentException: 25.0.2` when running on JDK 25+ — the system JDK on this diff --git a/sdk-transport-okhttp/build.gradle.kts b/sdk-transport-okhttp/build.gradle.kts index a89a4198..9acb3d07 100644 --- a/sdk-transport-okhttp/build.gradle.kts +++ b/sdk-transport-okhttp/build.gradle.kts @@ -8,13 +8,11 @@ plugins { kotlin("jvm") id("org.jetbrains.kotlinx.kover") - `maven-publish` - signing + // Publishing, signing, POM metadata, and coordinates come from this convention plugin + // (build-logic/src/main/kotlin/dexpace.published-module.gradle.kts). + id("dexpace.published-module") } -group = "org.dexpace" -version = "0.0.1-alpha.1" - // Java 8 bytecode is inherited from the root build script — this transport ships to JDK 8 // consumers just like `sdk-core` does. OkHttp 5.x itself supports JDK 8+ at runtime. // @@ -38,53 +36,6 @@ tasks.test { useJUnitPlatform() } -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") - } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") - } - } - } - } - repositories { - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) - } - } -} - -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) - } - sign(publishing.publications["library"]) -} - // Detekt runs normally on this module. It uses the JDK-8 toolchain, so it is unaffected by // the detekt 1.23.x `JavaVersion.parse` crash on non-8 toolchains (JDK 25+) that forces the // gate off on `sdk-transport-jdkhttp` (11) and `sdk-async-virtualthreads` (21) — see those diff --git a/settings.gradle.kts b/settings.gradle.kts index 1416c2a6..0aaf6f09 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,13 @@ * SPDX-License-Identifier: MIT */ +pluginManagement { + // `build-logic` is an included build that compiles this repository's convention plugins. + // Putting `includeBuild` here (rather than at the top level) lets modules apply those + // plugins by id from their own `plugins {}` block — e.g. `id("dexpace.published-module")`. + includeBuild("build-logic") +} + plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } From 5c1585817d37ed9ee4b525c421508bb4829d0eb2 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Tue, 16 Jun 2026 22:28:55 +0300 Subject: [PATCH 2/3] build: style-gate build-logic with ktlint, pin its toolchain, and guard the publication on the kotlin-jvm plugin --- build-logic/build.gradle.kts | 28 ++++++ .../dexpace.published-module.gradle.kts | 87 +++++++++++-------- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index a7de29e7..5512a694 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -12,9 +12,37 @@ // classpath. plugins { `kotlin-dsl` + // Style-gate this included build's own scripts. `build-logic` is a separate build with its + // own settings, so the root build's `subprojects { ktlint }` block does not reach it; without + // this the convention-plugin `.kts` files would escape the repository's Kotlin style checks. + // The version is pinned literally (not via the version catalog) to keep this build catalog-free + // — see the rationale in `settings.gradle.kts`; it must match `ktlint-plugin` in + // `gradle/libs.versions.toml`. + id("org.jlleitschuh.gradle.ktlint") version "12.1.1" } repositories { mavenCentral() gradlePluginPortal() } + +// Pin the toolchain so this included build compiles reproducibly regardless of the JDK running +// the Gradle daemon. This is plugin code for the build JVM, not shipped bytecode, so the version +// only needs to be recent enough for `kotlin-dsl`. +kotlin { + jvmToolchain(21) +} + +ktlint { + ignoreFailures.set(false) +} + +// `kotlin-dsl` adds its plugin wrappers and DSL accessors (under build/generated-sources) to the +// `main` Kotlin source set, so the source-set ktlint tasks would otherwise lint tool-generated +// code. Drop the generated tree from those tasks' inputs; the hand-written convention scripts under +// src/ — and the build script via `runKtlintCheckOverKotlinScripts` — are still checked. +val generatedSourcesDir = layout.buildDirectory.dir("generated-sources").get().asFile +tasks.withType().configureEach { + val handWritten = source.filter { file -> !file.startsWith(generatedSourcesDir) }.files + setSource(handWritten) +} diff --git a/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts index c9cdf5b6..39bfb218 100644 --- a/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts +++ b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts @@ -23,54 +23,65 @@ plugins { // Coordinates. The group and version are the same for every published module and match the // values declared on the root project; keeping the single literal here makes a coordinate -// change a one-file edit instead of a nine-file edit. +// change a one-file edit instead of a nine-file edit. The root `build.gradle.kts` still declares +// its own `group`/`version`, so a version bump must update both literals until they are sourced +// from one place (e.g. a `gradle.properties` entry read by both). group = "org.dexpace" version = "0.0.1-alpha.1" -publishing { - publications { - create("library") { - from(components["java"]) - pom { - name.set(project.name) - description.set("Dexpace Java SDK — ${project.name}") - url.set("https://github.com/dexpace/java-sdk") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") - distribution.set("repo") +// The `library` publication is built from the `java` software component, which only exists once a +// `java`/`java-library`/`kotlin("jvm")` plugin is applied. Every current consumer applies +// `kotlin("jvm")`, but this plugin neither applies nor requires one, so the whole publication + +// signing setup is guarded on the Kotlin JVM plugin. Without the guard, a module that opted in +// without a Java/Kotlin plugin would fail with an opaque "SoftwareComponent with name java not +// found". +pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + publishing { + publications { + create("library") { + from(components["java"]) + pom { + name.set(project.name) + description.set("Dexpace Java SDK — ${project.name}") + url.set("https://github.com/dexpace/java-sdk") + licenses { + license { + name.set("MIT License") + url.set("https://github.com/dexpace/java-sdk/blob/main/LICENSE") + distribution.set("repo") + } } - } - developers { - developer { - id.set("dexpace") - name.set("Dexpace SDK Team") + developers { + developer { + id.set("dexpace") + name.set("Dexpace SDK Team") + } + } + scm { + connection.set("scm:git:https://github.com/dexpace/java-sdk.git") + developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") + url.set("https://github.com/dexpace/java-sdk") } - } - scm { - connection.set("scm:git:https://github.com/dexpace/java-sdk.git") - developerConnection.set("scm:git:ssh://github.com/dexpace/java-sdk.git") - url.set("https://github.com/dexpace/java-sdk") } } } - } - repositories { - // Local staging repository. CI must override this to publish to a real remote. - maven { - name = "local" - url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) + repositories { + // Local staging repository. CI must override this to publish to a real remote. + maven { + name = "local" + url = uri(rootProject.layout.buildDirectory.dir("staging-repo")) + } } } -} -signing { - isRequired = (System.getenv("CI") == "true") - val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") - val signingPassword = project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") - if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { - useInMemoryPgpKeys(signingKey, signingPassword) + signing { + isRequired = (System.getenv("CI") == "true") + val signingKey = project.findProperty("signing.key") as String? ?: System.getenv("SIGNING_KEY") + val signingPassword = + project.findProperty("signing.password") as String? ?: System.getenv("SIGNING_PASSWORD") + if (!signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank()) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + sign(publishing.publications["library"]) } - sign(publishing.publications["library"]) } From f1da7327bbf4931e514121632858f125f5d1c73c Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Wed, 17 Jun 2026 00:20:51 +0300 Subject: [PATCH 3/3] build: source module coordinates from gradle.properties and document the publishing convention Set group/version once in gradle.properties, which Gradle applies to the root project and every subproject, removing the duplicated literals from the root build script and the published-module convention plugin. A coordinate bump is now a single one-line edit. Published POM coordinates are unchanged (org.dexpace / 0.0.1-alpha.1). Document the dexpace.published-module convention in CLAUDE.md so new modules apply the plugin rather than re-inlining a publishing block. --- CLAUDE.md | 6 ++++++ .../main/kotlin/dexpace.published-module.gradle.kts | 11 ++++------- build.gradle.kts | 4 ++-- gradle.properties | 8 ++++++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7675b78c..7cc02b9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,12 @@ Layered, from the bottom up: public entry point (e.g. only `OkioIoProvider` is public in `sdk-io-okio3`). - **`sdk-core` has zero non-SLF4J runtime deps** — I/O, Jackson, and concurrency libraries live only in adapter modules. SLF4J is `compileOnly` (added by the root build to every Kotlin module). +- **Published modules apply `id("dexpace.published-module")`** — the convention plugin in the `build-logic` + included build (`build-logic/src/main/kotlin/dexpace.published-module.gradle.kts`) carries the + `maven-publish` + `signing` setup, shared POM, staging repo, and CI-gated signing. Do not re-inline a + `publishing {}`/`signing {}` block in a module; a new publishable module just applies the plugin, and a + module that must not be published simply omits it. Coordinates (`group`/`version`) come from + `gradle.properties` and apply to every project. - **Commit style:** `feat:` / `test:` / `docs:` / `chore:` prefixes; `merge:` for work-unit merge commits. ## Things That Will Bite You diff --git a/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts index 39bfb218..20c4af50 100644 --- a/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts +++ b/build-logic/src/main/kotlin/dexpace.published-module.gradle.kts @@ -21,13 +21,10 @@ plugins { signing } -// Coordinates. The group and version are the same for every published module and match the -// values declared on the root project; keeping the single literal here makes a coordinate -// change a one-file edit instead of a nine-file edit. The root `build.gradle.kts` still declares -// its own `group`/`version`, so a version bump must update both literals until they are sourced -// from one place (e.g. a `gradle.properties` entry read by both). -group = "org.dexpace" -version = "0.0.1-alpha.1" +// Coordinates (`group`/`version`) are not set here. Gradle applies them from the repository-root +// `gradle.properties` to the root project and every subproject, so each consuming module already +// carries the shared `org.dexpace` coordinates and current version by the time this plugin runs — +// a coordinate bump is a one-line edit in that file. // The `library` publication is built from the `java` software component, which only exists once a // `java`/`java-library`/`kotlin("jvm")` plugin is applied. Every current consumer applies diff --git a/build.gradle.kts b/build.gradle.kts index 23501757..47875389 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,8 +38,8 @@ plugins { alias(libs.plugins.detekt) } -group = "org.dexpace" -version = "0.0.1-alpha.1" +// `group` and `version` are set once in `gradle.properties` and applied by Gradle to the root +// project and every subproject — see that file. // Coverage: aggregate every Kover-enabled subproject through this root project's reports. dependencies { diff --git a/gradle.properties b/gradle.properties index 7fc6f1ff..8ae7bc90 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,9 @@ kotlin.code.style=official + +# Project coordinates. Gradle applies `group` and `version` from gradle.properties to every +# project in this build, so the root and all published modules share one source of truth — no +# per-module or per-script literal. A coordinate bump is a one-line edit here. (The `build-logic` +# included build is a separate build with its own scope and does not read these; it needs no +# coordinates of its own.) +group=org.dexpace +version=0.0.1-alpha.1