Publishing¶
Publishing targets Maven Central Publisher Portal via the OSSRH Staging API compatibility endpoint, with in-memory PGP signing in CI.
Coordinates¶
- Group:
io.github.jdbenitez94.criollo.kmp.foundation - Namespace (Portal):
io.github.jdbenitez94(GitHub username namespace) - Version:
ProjectConfig.versionin build-logic (must match git tagvX.Y.Zandversion.txt) - Override:
-Pcriollo.version=…(CI snapshots use the next patch +-SNAPSHOT)
CHANGELOG blank lines (MD012)¶
release-please inserts an extra
blank line before ### sections. Codacy markdownlint (MD012) rejects that.
normalize-release-changelog.yml
runs on release-please--branches--* pushes and collapses consecutive blanks via
scripts/normalize-changelog-blanks.py.
Apple (iOS) targets¶
iosArm64 / iosSimulatorArm64 are registered only when Xcode is available, or when
-Pcriollo.requireAppleTargets=true is set (fails fast if Xcode is missing).
publish-maven-central.yml runs on
macos-15 with that flag so Maven Central module metadata includes Apple klibs.
Linux CI keeps skipping Apple targets (no Xcode).
0.1.5 and earlier Ubuntu publishes omitted iOS variants. 0.1.6 was built on macOS but
only coroutines-viewmodel reached repo1 after a partial Portal publish — treat 0.1.7+
as the first complete Central set (BOM + coroutines + adapters, including Apple klibs).
Local check:
./gradlew :coroutines:outgoingVariants -Pcriollo.requireAppleTargets=true | grep iosArm64ApiElements
Release flow (automated)¶
| Branch | Behavior |
|---|---|
dev |
Integration + SNAPSHOT publish; tip kept equal to main after every main push |
main |
release-please opens a release PR from conventional commits |
See Contributing — branch model for the
PR loop and sync-dev-to-main automation.
Snapshots (dev)¶
On push to dev (non-docs), publish-snapshots.yml
publishes {nextPatch}-SNAPSHOT (e.g. ProjectConfig 0.1.4 → 0.1.5-SNAPSHOT) to
maven-snapshots.
No Portal finalize / manual publish step. Snapshots expire (~90 days).
Consume:
repositories {
mavenCentral()
maven {
name = "Central Portal Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
mavenContent { snapshotsOnly() }
}
}
dependencies {
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines:0.1.5-SNAPSHOT")
}
Local: ./gradlew publishAllPublicationsToMavenCentralRepository -Pcriollo.version=0.1.5-SNAPSHOT
(requires Central + signing props in local.properties).
- Merge work to
main(via PR). Prefer Conventional Commits. - release-please opens/updates a PR that bumps
version.txt,ProjectConfig.version,.release-please-manifest.json,CHANGELOG.md, and the BOM version inREADME.md(// x-release-please-version). - Merge that release PR → release-please creates tag
vX.Y.Z+ GitHub Release. - The same workflow run publishes to Maven Central (see
publish-maven-central.yml). - CI builds a local Maven repo bundle, asserts every expected POM is present,
uploads one zip via the Portal Publisher API
(
publishingType=AUTOMATIC), polls untilPUBLISHED, then verifies the full set on repo1. Incomplete OSSRH staging publishes (0.1.6–0.1.8) are no longer used for releases. Status: https://central.sonatype.com/publishing.
Bootstrap: initial-version is 0.1.0 so the first release PR is 0.1.0. After that, SemVer follows commit types.
Manual tag (escape hatch)¶
git tag v0.1.0 && git push origin v0.1.0
release.yml publishes on v* tag pushes and creates a
GitHub Release only if one does not already exist.
Local smoke¶
./gradlew publishToMavenLocal
Artifacts land under ~/.m2/repository/io/github/jdbenitez94/criollo/kmp/foundation/,
including:
bom(Java Platform / dependencyManagement for all modules)- library modules (
coroutines,coroutines-compose,coroutines-viewmodel— Gradle:coroutines,:coroutines:compose,:coroutines:viewmodel) project-conventionsGradle plugin (+ plugin-marker POM for idio.github.jdbenitez94.criollo.kmp.foundation.project-conventions)
GPG signing key¶
Key material lives in the machine keyring and gitignored files under config/
(**/*.gpg is ignored). Public key: config/public-key.asc.
Create a project signing key the same way as Saveable (RSA-4096, comment = repo name):
gpg --batch --pinentry-mode loopback --passphrase-file <(openssl rand -base64 32) --gen-key <<'EOF'
Key-Type: RSA
Key-Length: 4096
Key-Usage: sign,cert
Subkey-Type: RSA
Subkey-Length: 4096
Subkey-Usage: encrypt
Name-Real: Joaquin Daniel Benitez
Name-Comment: criollo-kmp-foundation
Name-Email: jdbenitez94@gmail.com
Expire-Date: 0
%commit
EOF
FPR="$(gpg --list-secret-keys --with-colons 'criollo-kmp-foundation' | awk -F: '/^fpr:/ {print $10; exit}')"
gpg --export-secret-keys "$FPR" > config/secret-key-ring.gpg
gpg --export --armor "$FPR" > config/public-key.asc
# CI needs ASCII-armored secret: gpg --export-secret-keys --armor "$FPR"
Upload the public key (once) so Central can verify signatures, e.g. https://keys.openpgp.org/
Secrets and variables¶
GitHub secrets: OSSRH_USERNAME, OSSRH_PASSWORD (Central Portal user token),
SIGNING_KEY_ID, SIGNING_KEY, SIGNING_PASSWORD.
GitHub variable: MAVEN_CENTRAL_NAMESPACE = io.github.jdbenitez94.
Local secret store¶
All publish/signing secrets for this machine live in gitignored local.properties
(mode 0600), including the ASCII-armored signingInMemoryKey. The convention plugin
reads Gradle/-P/env first, then falls back to that file.
Also mirrored (gitignored): config/secret-key-ring.gpg, config/signing-secret.key.
Public key: config/public-key.asc.
Omit signing props / set signing.required=false for unsigned publishToMavenLocal.
To rotate the Central Portal user token, generate a new one at
https://central.sonatype.com/usertoken, replace mavenCentralUsername /
mavenCentralPassword in local.properties, then ask the agent to resync secrets (or):
awk -F= '/^mavenCentralUsername=/{print substr($0,index($0,"=")+1)}' local.properties \
| gh secret set OSSRH_USERNAME -R jdbenitez94/criollo-kmp-foundation
awk -F= '/^mavenCentralPassword=/{print substr($0,index($0,"=")+1)}' local.properties \
| gh secret set OSSRH_PASSWORD -R jdbenitez94/criollo-kmp-foundation