The Phase 6 release step. It is deliberately not automated: publishing a released version to Maven Central is irreversible (an accepted version can never be re-uploaded or deleted) and requires secret credentials (a signing key and a Central Portal token). This page is the checklist a human follows; the safe local half can be run any time.
The Kotlin Multiplatform plugin produces one Maven publication per target plus an umbrella, not a single artifact:
| Publication | Coordinates (example) | Role |
|---|---|---|
kotlinMultiplatform (root) |
io.spine:elastic:<v> |
umbrella; carries Gradle Module Metadata (.module) that redirects common consumers to the right per-target artifact |
jvm |
io.spine:elastic-jvm:<v> |
JVM jar + POM |
macosArm64 |
io.spine:elastic-macosarm64:<v> |
Apple-silicon klib |
linuxX64, linuxArm64, mingwX64, iosArm64, iosSimulatorArm64 |
elastic-<target> |
one klib each |
Each set is the primary artifact + -sources.jar + -javadoc.jar + .pom (+ the
root .module), each signed (.asc) with checksums. The coordinate suffix is
the lowercased target name (-macosarm64, not -macosArm64).
Host constraint: the Apple-silicon klibs can only be built on macOS. Publish the whole set from one macOS host in one shot — splitting the publication across hosts causes Central to reject the deployment. This is why a KMP release runs on a Mac (it can build everything a Linux runner can, plus the Apple klibs).
publishToMavenLocal needs no credentials and no network, writes only to
~/.m2/repository, and is fully reversible (delete the directory). It is the correct
smoke test of the publication wiring, POM contents, and artifact set.
# The version gate first: a stale version would clobber the Maven-Local artifact
# that consumer integration tests rely on. (This repo enforces it via a hook.)
./gradlew :elastic:publishToMavenLocal
ls -R ~/.m2/repository/io/spine/elastic*/ # inspect the full artifact setAs of this writing the
elasticmodule applieskmp-modulebut notkmp-publish, so it registers no publication tasks yet. Wiring publication is the first release task below; until then,publishToMavenLocalproduces only the default KMP publications (no docs/sources jars, no signing).
The repo already ships the Spine convention for KMP publishing — apply it on
elastic:
// elastic/build.gradle.kts
plugins {
`kmp-module`
`kmp-publish` // adds maven-publish + attaches the docs jars
}
spinePublishing {
// The Spine convention (io.spine.gradle.publish.PublishingRepos) ships exactly two
// remote destinations: Cloud Artifact Registry and GitHub Packages. Maven Central is
// NOT one of them — see the section below to add it.
destinations = PublishingRepos.run { setOf(cloudArtifactRegistry, gitHub("elastic")) }
customPublishing = true // required: spinePublishing delegates KMP to kmp-publish
}kmp-publish attaches the HTML-docs jar to the kotlinMultiplatform and jvm
publications; spinePublishing fills in the POM (name, description, url, licenses,
developers, scm) and the destination repositories. Validate with
publishToMavenLocal before configuring any remote destination. Publishing to the
configured Spine remotes uses the per-repository task Gradle derives from the
destination name, e.g. publishAllPublicationsToCloudArtifactRegistryRepository.
Maven Central is not a Spine-convention destination — PublishingRepos defines
only Cloud Artifact Registry and GitHub Packages. Releasing to Central therefore
requires wiring it explicitly first, and the exact Gradle task name follows the
repository you configure (there is no built-in ...ToMavenCentralRepository task).
Prerequisites (all must hold):
- A configured Maven Central destination. Add a Central Portal repository to the
build — either a
publishing.repositories { maven { name = "..."; url = ... } }block (itsnamedetermines the task) or a Central-publishing plugin such ascom.vanniktech.maven.publish(which registers its ownpublishToMavenCentraltask). Nothing in the current build wires this. - A non-SNAPSHOT version. Central rejects
-SNAPSHOT. The project is currently on a1.0.0-SNAPSHOT-NNNline; a Central release needs a real1.0.0-class version (a separate, deliberate version decision — not this phase's snapshot bump). - A claimed/verified namespace for
io.spineon the Central Portal (central.sonatype.com— the legacy OSSRH /oss.sonatype.orgpath reached end-of-life on 2025-06-30; new deployments use the Portal). - A Portal token (a generated token, not the login password):
ORG_GRADLE_PROJECT_mavenCentralUsername/...Password. - A PGP signing key, published to a public keyserver, provided in memory:
ORG_GRADLE_PROJECT_signingInMemoryKey(+...KeyPassword, +...KeyIdfor a subkey). Every artifact must carry a.ascsignature. - A complete POM: name, description, url, licenses, developers, scm — filled by
spinePublishing.
Release (once prerequisite 1 is wired — substitute your configured repository / plugin task name; do not assume a fixed one):
# Build + sign all publications and stage them to the Portal. Still requires a manual
# "release" click in the Portal UI (or an auto-release variant, which is one-way).
# Some plugin versions need --no-configuration-cache.
# ./gradlew publishAllPublicationsTo<YourCentralRepoName>Repository
# or, with the vanniktech plugin:
# ./gradlew publishToMavenCentralThen verify the full artifact set (root .module, every per-target jar,
-sources/-javadoc, .pom, .asc, checksums) appears before releasing.
| Step | Class |
|---|---|
publishToMavenLocal, footprint/JMH runs, POM generation |
safe / local / reversible |
Wiring kmp-publish + spinePublishing in the build |
safe (a build change; validate locally) |
| Signing locally (needs the private key) | local, but touches a secret |
| Claiming the namespace, generating a Portal token | one-time, needs Portal login |
| Wiring a Maven Central destination (repository or plugin) | safe (a build change; not a default Spine destination) |
| The configured Central-publish task (name follows your repo/plugin) | needs secret credentials; released versions are permanent and immutable |
The last row is the human-gated action. Everything above it is reproducible without secrets and is what this phase delivers and validates.