Skip to content

feat(kotlin-sdk): add maven-publish for the release AAR#4182

Merged
shumkov merged 9 commits into
dashpay:v4.1-devfrom
bfoss765:port/v4.1/maven-publish
Jul 22, 2026
Merged

feat(kotlin-sdk): add maven-publish for the release AAR#4182
shumkov merged 9 commits into
dashpay:v4.1-devfrom
bfoss765:port/v4.1/maven-publish

Conversation

@bfoss765

@bfoss765 bfoss765 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Adds maven-publish for the kotlin-sdk release AAR under the org.dashj group, with a JReleaser Maven Central/Sonatype deploy pipeline, a javadoc jar for Central, a JNI-presence guard that hard-fails remote publishes (and wipes staging-deploy before every staging publish), and a PUBLISHING.md runbook.

Re-opens #4045 which was auto-closed when the #3999 base branch was deleted; rebased onto v4.1-dev. All six original commits replayed cleanly — no hunks needed to be dropped as already-absorbed.

Verified: :sdk:assembleRelease builds the AAR, JReleaser/publish task graph configures, and the 175 sdk unit tests pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Android SDK can now be published to Maven Central and Sonatype snapshots.
    • Published artifacts now include source and Javadoc documentation.
    • Expanded release workflow support via automated deployment.
  • Bug Fixes

    • Added checks to block publishing when required native JNI libraries are missing from the staged artifacts.
  • Documentation

    • Added a publishing guide covering prerequisites, verification, signing, and how to consume the SDK from Maven Central.

Provenance notes (moved out of code comments per review)

Decision in code Origin
Publish under the existing org.dashj group (build.gradle.kts, coordinate block) HashEngineering's namespace call on PR #4045
Javadoc jar required — Central Portal rejects non-POM components without one (applyMavenCentralRules(true)) #4045
In-memory signing-key wiring so CI release via ORG_GRADLE_PROJECT_signingKey doesn't fail in signReleasePublication #4045
-PallowMissingJni escape closed for remote/staging publishes #4045, finding 8b899bc2e5e8
MasternodeEntry kept internal so its generated serializer() stays out of the public ABI (Sdk.kt) #4045, finding 96b7b2a236ff

Review-response commits

  • d517ba20bc — POM license URL → LICENSE.md; tracking refs moved from comments to this table
  • 9190ba3059 — new verifyStagedAarForRemotePublish: before any jreleaser deploy, the staged repo must contain exactly the intended GAV, no stray artifacts, exactly one correctly-named release AAR, and that AAR must carry jni/arm64-v8a/libdash_sdk_jni.so + jni/x86_64/libdash_sdk_jni.so
  • 33b2a57188 — round-2 hardening: empty/directory-shaped zip entries rejected (0-byte .so from an interrupted build no longer passes), source-tree guard also requires content, guards made configuration-cache safe

bfoss765 and others added 6 commits July 21, 2026 07:52
Publishes org.dashfoundation:dash-sdk-android with sources jar and POM
(Room/DataStore/Biometric api deps carried). publishToMavenLocal works today;
a remote repository block can be added when a hosting decision is made.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…api; POM scm/developers

Address review of the release-AAR maven-publish.

- Fail the publish tasks (verifyJniLibsPresent, wired to
  publishToMavenLocal / publishToMavenRepository) when
  src/main/jniLibs/<abi>/libdash_sdk_jni.so is absent for the arm64-v8a /
  x86_64 ABI policy build_android.sh produces. That directory is gitignored
  and empty in a clean checkout, so without this a publish could ship a
  coordinate that installs but dies at runtime with UnsatisfiedLinkError.
  Escape hatch: -PallowMissingJni warns and continues (metadata-only dry run).
- Move kotlinx-coroutines-core from implementation to api: the public SDK
  surface returns Flow/StateFlow (Room DAOs, PlatformWalletManager.pendingIdentityKeys),
  so consumers of the coordinate need it on their compile classpath.
- Add POM scm + developers sections (required for Maven Central / remote POM
  validation), pointed at github.com/dashpay/platform rather than a vanity
  domain, plus a note that the org.dashfoundation groupId can't go to Maven
  Central until the matching domain is owned (per HashEngineering).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l/Sonatype deploy

Per HashEngineering's decision on PR dashpay#4045, switch the published Maven
coordinate group from org.dashfoundation to org.dashj. Dash already owns
dashj.org and ships its legacy Core/Platform SDKs there, so the org.dashj
group needs no separate domain verification for Maven Central; the
org.dashfoundation group would have (dashfoundation.org is third-party-held).
This is a coordinates-only change: the Kotlin source packages stay
org.dashfoundation.dashsdk, and the artifactId stays dash-sdk-android.

Add a jreleaser deploy config mirroring dashj/core/build.gradle: maven-publish
stages the signed artifacts into build/staging-deploy, and jreleaserDeploy
uploads release versions to Maven Central (Sonatype portal) and -SNAPSHOT
versions to the Sonatype snapshots repo. Signing + Sonatype credentials are
read from jreleaser env/props at deploy time only.

maven-publish still drives publishToMavenLocal / internal-repo publishing.
The gradle signing plugin is guarded to require a key only when a real
PublishToMavenRepository task is in the graph, so publishToMavenLocal and
plain assembles need no GPG setup.

Validated: `./gradlew :sdk:publishToMavenLocal` lands
org.dashj:dash-sdk-android:0.1.0-SNAPSHOT under ~/.m2/repository/org/dashj/
with signReleasePublication SKIPPED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…; wire in-memory signing keys

Addresses the latest dashpay#4045 review round.

- Missing javadoc jar (blocking 3acc85ab85d4): singleVariant("release") now calls
  withJavadocJar() in addition to withSourcesJar(). Maven Central's Publisher
  Portal rejects any non-POM component without a javadoc jar, and the snapshot
  deployer's applyMavenCentralRules(true) enforces it before upload. AGP emits a
  Kotlin-appropriate javadoc jar — no Dokka dependency added.

- JNI-less AAR guard (blocking 8b899bc2e5e8): -PallowMissingJni previously
  downgraded verifyJniLibsPresent to a warning for EVERY publish task, so it could
  push a nativeless AAR to a remote repo / jreleaser's staging dir. Split into
  verifyJniLibsForRemotePublish (ALWAYS hard-fails; flag not honored — wired to
  every PublishToMavenRepository and to jreleaserDeploy/Upload/Release) and
  verifyJniLibsForLocalPublish (honors the flag; wired to PublishToMavenLocal).
  Verified end-to-end: remote task fails with -PallowMissingJni; local warns.

- In-memory signing keys (blocking d251acfba63b, from last round): the signing
  block documented ORG_GRADLE_PROJECT_signingKey/signingPassword but never called
  useInMemoryPgpKeys, so a CI release on that path failed in signReleasePublication.
  Now reads the signingKey/signingPassword properties and calls useInMemoryPgpKeys
  when present; absent (local dev) it's skipped and required=false needs no key.

- kotlinx-serialization-json scope (suggestion 96b7b2a236ff): Sdk.MasternodeEntry
  is a wire DTO touched only by a private envelope and internal parse — never
  returned by a public function. Marked it `internal` so its generated serializer
  isn't public API; the dependency correctly stays `implementation` (cleaner than
  promoting it to `api`, the reviewer's own preferred fix).

Validated: :sdk:publishToMavenLocal lands sources + javadoc jars under
~/.m2/repository/org/dashj/; :sdk unit suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al release

One-page post-merge runbook now that hosting is settled on Maven Central
under org.dashj (dashpay#4045): prerequisites (Central Portal
token for org.dashj, GPG key, NDK/cargo-ndk toolchain), the env-var-only
credential wiring (ORG_GRADLE_PROJECT_signingKey/Password for the staging
publish, JRELEASER_MAVENCENTRAL_SONATYPE_* / JRELEASER_NEXUS2_SNAPSHOTS_* /
JRELEASER_GPG_* for the deploy), the three release commands in order
(build_android.sh --verify -> staged publish -> jreleaserDeploy) with
-PsdkVersion for real releases (default stays 0.1.0-SNAPSHOT), what the
verifyJniLibsForRemotePublish guard refuses and why, and the consumer
coordinate org.dashj:dash-sdk-android:<version>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
maven-publish adds/replaces the current coordinates but never removes other
versions' directories from sdk/build/staging-deploy, so a release deploy run
after an earlier snapshot/release staging could hand JReleaser stale
coordinates. cleanStagingDeploy now runs before every *ToStagingRepository
publish (verified in the task graph via --dry-run); PUBLISHING.md notes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Kotlin SDK now defines Maven publication metadata, release and snapshot deployment through JReleaser, conditional signing, JNI validation for published artifacts, and documentation for publishing and consumption. MasternodeEntry is made internal.

Changes

Kotlin SDK release

Layer / File(s) Summary
Publication identity and API surface
packages/kotlin-sdk/sdk/build.gradle.kts, packages/kotlin-sdk/gradle/libs.versions.toml, packages/kotlin-sdk/sdk/src/main/kotlin/.../Sdk.kt
The SDK uses org.dashj coordinates and sdkVersion, exposes coroutines through api, pins and applies JReleaser, and makes MasternodeEntry internal.
Maven artifacts and signing
packages/kotlin-sdk/sdk/build.gradle.kts
Release publishing adds sources and Javadoc jars, staging cleanup, POM metadata, and conditional in-memory PGP signing.
JNI validation and release targets
packages/kotlin-sdk/sdk/build.gradle.kts
Publishing tasks require JNI libraries for the required ABIs, staged AAR contents are inspected, and JReleaser configures release and snapshot Sonatype targets.
Publishing workflow documentation
packages/kotlin-sdk/PUBLISHING.md
The guide documents coordinates, credentials, prerequisites, commands, JNI guards, post-deploy verification, and consumption requirements.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Gradle
  participant verifyJniLibsForRemotePublish
  participant StagingRepository
  participant verifyStagedAarForRemotePublish
  participant JReleaser
  participant Sonatype
  Gradle->>verifyJniLibsForRemotePublish: Verify required source JNI libraries
  Gradle->>StagingRepository: Publish signed AAR and metadata
  Gradle->>verifyStagedAarForRemotePublish: Validate staged layout and AAR JNI entries
  JReleaser->>StagingRepository: Read staged artifacts
  JReleaser->>Sonatype: Deploy release or snapshot
Loading

Suggested reviewers: quantumexplorer, bezibalazs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Maven publishing for the Kotlin SDK release AAR.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 26 ahead in queue (commit 33b2a57)
Queue position: 27/72 · 2 reviews active
ETA: start ~01:57 UTC · complete ~02:26 UTC (median 28m across 30 recent reviews; 2 slots)
Queued 1d ago · Last checked: 2026-07-22 19:40 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/kotlin-sdk/sdk/build.gradle.kts (1)

192-216: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Dual signing: Gradle signing plugin and JReleaser both sign the same staged artifacts.

The signing {} block here signs the release publication during publishReleasePublicationToStagingRepository (using ORG_GRADLE_PROJECT_signingKey/signingPassword), and separately jreleaser { signing { active.set(Active.ALWAYS) } } re-signs the staged files during jreleaserDeploy (using JRELEASER_GPG_*). Public JReleaser deploy logs confirm its own [sign] step signs the already-staged jar/pom/sources/javadoc files at deploy time, so this second signing pass is redundant with the first.

This isn't necessarily broken — JReleaser likely just overwrites the .asc files it finds — but it doubles the credential surface (two independent GPG key configurations must both be kept valid and in sync) and adds maintenance overhead for no clear benefit, since JReleaser alone is sufficient to produce Central-compliant signatures.

Worth confirming whether the Gradle-side signing {} block can be dropped entirely (letting maven-publish stage unsigned artifacts and JReleaser sign+deploy them, as shown in JReleaser's own Maven Central example), which would also simplify PUBLISHING.md's credentials section to a single GPG key path.

Also applies to: 295-344

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/kotlin-sdk/sdk/build.gradle.kts` around lines 192 - 216, Remove the
Gradle-side signing configuration from the build script, including the `signing
{}` block and its `useInMemoryPgpKeys`, `setRequired`, and `sign` calls, so
`maven-publish` stages unsigned artifacts and JReleaser remains the sole signer.
Update the related JReleaser publishing credential documentation to describe
only its GPG key configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/kotlin-sdk/sdk/build.gradle.kts`:
- Around line 192-216: Remove the Gradle-side signing configuration from the
build script, including the `signing {}` block and its `useInMemoryPgpKeys`,
`setRequired`, and `sign` calls, so `maven-publish` stages unsigned artifacts
and JReleaser remains the sole signer. Update the related JReleaser publishing
credential documentation to describe only its GPG key configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 38fce34f-5bfc-419f-a0f0-3766c99ec06b

📥 Commits

Reviewing files that changed from the base of the PR and between 8b466ab and 60c0280.

📒 Files selected for processing (4)
  • packages/kotlin-sdk/PUBLISHING.md
  • packages/kotlin-sdk/gradle/libs.versions.toml
  • packages/kotlin-sdk/sdk/build.gradle.kts
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/Sdk.kt

@shumkov

shumkov commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Publishing mechanics look sound (JNI guard gating remote publishes, staging wipe, Portal/nexus2 routing) and it merges clean against current v4.1-dev. Items before merge:

  • CI: the emulator failure (InvalidKeyException in DashPayUnlockAndSyncTest etc.) predates fix(kotlin-sdk): harden identity-key Keystore recovery and reconcile parity docs #4172's Keystore-recovery hardening — please re-run; the diff doesn't touch the failing code.
  • POM license URL points at blob/master/LICENSE — the file is LICENSE.md, so the link 404s and Central will publish it verbatim.
  • Comments embed tracking refs ("PR feat(kotlin-sdk): add maven-publish for the release AAR #4045", "finding 8b899bc2e5e8", "finding 96b7b2a236ff") — please move those to the PR description; the rationale text itself is good and should stay.
  • Non-blocking follow-up: nothing ties -PsdkVersion on the manual Maven publish to the kotlin-sdk-* tag that drives the GitHub-release AAR, so the two channels can ship different commits under one version. Suggest a follow-up folding jreleaserDeploy into kotlin-sdk-release.yml with the version derived from the tag (understood the Portal token is currently personal).

@shumkov

shumkov commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Additional review finding after checking the existing threads:

The remote publish guard verifies the source JNI directory, not the AAR that JReleaser will upload. verifyJniLibsForRemotePublish checks src/main/jniLibs, while jreleaserDeploy consumes the already-populated build/staging-deploy repository. A valid current source tree can therefore green-light a stale or malformed staged AAR.

Please inspect the staged release AAR immediately before deploy and require both jni/arm64-v8a/libdash_sdk_jni.so and jni/x86_64/libdash_sdk_jni.so, along with exactly the intended GAV/version. This is separate from the already-raised license URL, release-version coupling, and tracking-reference comments.

bfoss765 and others added 3 commits July 21, 2026 15:02
…comments

The POM licenses/license/url pointed at blob/master/LICENSE, but the file in
this repo is LICENSE.md, so the published link would 404 — point it at
LICENSE.md.

Also remove review-tracking tokens (PR/finding ids) from code comments,
keeping the rationale text intact; the refs move to the PR description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
verifyJniLibsForRemotePublish checks the source src/main/jniLibs tree at
staging time, but jreleaserDeploy uploads whatever already sits in
build/staging-deploy — a valid current source tree could green-light a stale
or malformed staged AAR.

Add verifyStagedAarForRemotePublish, which runs immediately before any
jreleaser deploy/upload/release task and hard-fails unless:
  1. staged artifacts live under exactly the intended
     <group>/<artifactId>/<version> path, with nothing staged outside it
     (jreleaser uploads the whole staging repository, strays included);
  2. exactly one release AAR is staged, named for the intended
     artifactId+version (plain, or Maven unique-snapshot timestamped);
  3. the AAR, opened as a zip, contains jni/arm64-v8a/libdash_sdk_jni.so
     AND jni/x86_64/libdash_sdk_jni.so.

The source-tree guard stays (staging-time check); this adds the deploy-time
check on what is actually uploaded. All filesystem work happens in doLast, and
only jreleaser tasks depend on the new guard, so publishToMavenLocal and plain
builds are unaffected. The published GAV is hoisted into
publishGroupId/publishArtifactId so the publication and the guard share one
source of truth. PUBLISHING.md documents the new guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ish guards

Review round 2: ZipFile.getEntry falls back to directory entries and a
0-byte .so passes an existence-only check, so both the source-tree and
staged-AAR guards now require a real file entry with content. Guard
lambdas capture plain values instead of script functions, keeping them
configuration-cache safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bfoss765

Copy link
Copy Markdown
Contributor Author

All items are done and pushed on this branch: license URL corrected to LICENSE.md, tracking refs moved into the PR description, and verifyStagedAarForRemotePublish now inspects the actual staged artifact — exact GAV, no strays, exactly one correctly-named AAR, both ABI .so entries — before any JReleaser deploy. Our own review round then hardened it further: zero-byte or directory-shaped .so entries are rejected (ZipFile.getEntry falls back to directory entries, and an interrupted build_android.sh leaves 0-byte files that passed an existence-only check), and the guards are configuration-cache safe.

The version-coupling follow-up you suggested is also ready as its own PR: jreleaserDeploy folded into kotlin-sdk-release.yml with the version derived only from the kotlin-sdk-v* tag (whole-string validated), gated on all five publishing secrets being present — absent skips with a notice, partial hard-fails at the gate.

@shumkov
shumkov merged commit c8ba109 into dashpay:v4.1-dev Jul 22, 2026
23 checks passed
@shumkov

shumkov commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Re-verified the pushed fixes in two independent review passes — all three items are genuinely delivered: the license URL resolves, tracking refs moved to the description, and verifyStagedAarForRemotePublish inspects the actual staged artifact (exact GAV, stray-file rejection, real non-empty JNI .so entries inside the AAR, no -PallowMissingJni bypass on remote paths). Additional dynamic verification: :sdk:testDebugUnitTest passes, the local publish verification passes twice under Gradle configuration-cache storage/reuse, and the remote staging guard fails closed for a deliberately absent coordinate. Ready from our side — remaining gates are the runner-infra CI failure (keychain permissions on the mac runner, not this PR) and human approval. #4193 correctly couples the release version to the tag as the follow-up.

bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 24, 2026
The manual Maven publish took -PsdkVersion by hand, so nothing tied it to
the kotlin-sdk-* tag that drives the GitHub-release AAR — the two channels
could ship different commits under one version (raised by shumkov on dashpay#4182).

Fold jreleaserDeploy into kotlin-sdk-release.yml:

- The Maven version is DERIVED from the tag (kotlin-sdk-vX.Y.Z -> X.Y.Z);
  there is no version input anywhere in the workflow, and the checkout is
  already pinned to the same tag, so the Maven Central artifact and the
  GitHub-release AAR are always the same commit + version. Malformed
  kotlin-sdk-v* tags and tag-derived -SNAPSHOT versions fail the run;
  non-versioned kotlin-sdk-* tags keep their AAR-only behavior.
- Deploy steps are gated on the Sonatype Central Portal secrets being
  configured (the token is currently personal): absent secrets skip with a
  notice, a partial secret set fails rather than guessing.
- The existing publish guards (cleanStagingDeploy,
  verifyJniLibsForRemotePublish, verifyStagedAarForRemotePublish) run in
  the CI path via the same task dependencies (verified with --dry-run).
- workflow_dispatch stays for re-runs/emergencies and is also tag-derived
  (existing-tag input only).
- PUBLISHING.md: document the CI path as preferred and mark the manual
  runbook as the fallback, pinned to the tag's commit/version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants