-
Notifications
You must be signed in to change notification settings - Fork 56
feat(kotlin-sdk): add maven-publish for the release AAR #4045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/kotlin-sdk-and-example-app
Are you sure you want to change the base?
Changes from all commits
b27971b
3d05a4f
b0b8d61
fcaa91f
a2e94fc
ffeeb82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Publishing `org.dashj:dash-sdk-android` to Maven Central | ||
|
|
||
| The SDK publishes under the existing **`org.dashj`** namespace (same as | ||
| `dashj-core`), so no new Central namespace verification is needed. This is a | ||
| Maven-coordinates decision only — the Kotlin source packages remain | ||
| `org.dashfoundation.dashsdk`. Deployment uses **JReleaser**, mirroring | ||
| [dashj/core/build.gradle](https://github.com/dashpay/dashj/blob/master/core/build.gradle): | ||
| release versions go to Maven Central via the Central Portal; `-SNAPSHOT` | ||
| versions go to the Sonatype snapshots repository. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - **Sonatype Central Portal token** for the `org.dashj` namespace (held by | ||
| HashEngineering — the same account that publishes `dashj-core`). | ||
| - **GPG signing key** (the dashj release key works), with the public key on the | ||
| keyservers. | ||
| - **Native toolchain**: Android NDK r28+ (`28.1.13356709`), `cargo-ndk`, rustup | ||
| targets `aarch64-linux-android` and `x86_64-linux-android`, JDK 17. | ||
|
|
||
| ## Credentials — environment only, never committed | ||
|
|
||
| Gradle signing (signs artifacts during the staging publish; the classic | ||
| `signing.keyId` / `signing.password` / `signing.secretKeyRingFile` entries in | ||
| `~/.gradle/gradle.properties`, as used for dashj-core, also work): | ||
|
|
||
| ```bash | ||
| export ORG_GRADLE_PROJECT_signingKey="$(cat private-key.asc)" # ASCII-armored | ||
| export ORG_GRADLE_PROJECT_signingPassword="…" | ||
| ``` | ||
|
|
||
| JReleaser deploy (read only when a `jreleaser*` task runs): | ||
|
|
||
| ```bash | ||
| export JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME="…" # Central Portal token name | ||
| export JRELEASER_MAVENCENTRAL_SONATYPE_PASSWORD="…" # Central Portal token value | ||
| export JRELEASER_NEXUS2_SNAPSHOTS_USERNAME="…" # same token (snapshots repo) | ||
| export JRELEASER_NEXUS2_SNAPSHOTS_PASSWORD="…" | ||
| export JRELEASER_GPG_PUBLIC_KEY="$(cat public-key.asc)" | ||
| export JRELEASER_GPG_SECRET_KEY="$(cat private-key.asc)" | ||
| export JRELEASER_GPG_PASSPHRASE="…" | ||
| ``` | ||
|
|
||
| ## Release steps (from `packages/kotlin-sdk/`) | ||
|
|
||
| ```bash | ||
| # 1. Build the native JNI library (arm64-v8a + x86_64) and verify its exports. | ||
| ./build_android.sh --verify | ||
|
|
||
| # 2. Stage the signed release AAR + sources/javadoc jars into sdk/build/staging-deploy. | ||
| ./gradlew :sdk:publishReleasePublicationToStagingRepository -PsdkVersion=0.1.0 | ||
| # (the staging dir sdk/build/staging-deploy is wiped automatically before every | ||
| # staging publish — cleanStagingDeploy — so stale versions from earlier runs | ||
| # can never reach JReleaser) | ||
|
|
||
| # 3. Upload the staged artifacts. Release versions -> Maven Central; | ||
| # -SNAPSHOT versions -> Sonatype snapshots. | ||
| ./gradlew :sdk:jreleaserDeploy -PsdkVersion=0.1.0 | ||
| ``` | ||
|
|
||
| Then approve/verify the deployment at <https://central.sonatype.com> and | ||
| confirm the artifact appears on search.maven.org (sync takes ~30 min). | ||
|
|
||
| **Versioning:** the default is `0.1.0-SNAPSHOT`; pass the same | ||
| `-PsdkVersion=x.y.z` to steps 2 and 3. The first release should be a real | ||
| version (e.g. `0.1.0`) — the Android wallet's CI must depend on a pinned | ||
| release, never a snapshot. | ||
|
|
||
| ## The JNI guard | ||
|
|
||
| `sdk/src/main/jniLibs/` is gitignored, so a clean checkout has no | ||
| `libdash_sdk_jni.so` — and an AAR published without it installs fine but dies | ||
| at runtime with `UnsatisfiedLinkError`. The `verifyJniLibsForRemotePublish` | ||
| task therefore **hard-fails** every staging/remote/jreleaser publish task when | ||
| the `.so` is missing for `arm64-v8a` or `x86_64`. There is no override for | ||
| remote publishes; `-PallowMissingJni` only downgrades the check for | ||
| `publishToMavenLocal` (metadata-only dry runs into `~/.m2`). If the guard | ||
| fires, run step 1. | ||
|
|
||
| ## Consuming the artifact | ||
|
|
||
| ```kotlin | ||
| repositories { mavenCentral() } | ||
| dependencies { implementation("org.dashj:dash-sdk-android:0.1.0") } | ||
| ``` | ||
|
|
||
| Requires `minSdk 29`. The AAR bundles the native library for `arm64-v8a` and | ||
| `x86_64`; `armeabi-v7a` is intentionally unsupported. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,30 @@ | ||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.Callable | ||||||||||||||||||||||||||||||||||||
| import org.gradle.api.publish.maven.tasks.PublishToMavenLocal | ||||||||||||||||||||||||||||||||||||
| import org.gradle.api.publish.maven.tasks.PublishToMavenRepository | ||||||||||||||||||||||||||||||||||||
| import org.jreleaser.model.Active | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| plugins { | ||||||||||||||||||||||||||||||||||||
| alias(libs.plugins.android.library) | ||||||||||||||||||||||||||||||||||||
| alias(libs.plugins.kotlin.android) | ||||||||||||||||||||||||||||||||||||
| alias(libs.plugins.kotlin.serialization) | ||||||||||||||||||||||||||||||||||||
| alias(libs.plugins.ksp) | ||||||||||||||||||||||||||||||||||||
| `maven-publish` | ||||||||||||||||||||||||||||||||||||
| id("signing") | ||||||||||||||||||||||||||||||||||||
| alias(libs.plugins.jreleaser) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Published Maven coordinate group. Per HashEngineering's call on PR #4045: | ||||||||||||||||||||||||||||||||||||
| // publish under the existing `org.dashj` group — Dash already owns dashj.org and | ||||||||||||||||||||||||||||||||||||
| // ships its legacy Core/Platform SDKs there, so this needs no separate domain | ||||||||||||||||||||||||||||||||||||
| // verification for Maven Central (`org.dashfoundation` would have, since | ||||||||||||||||||||||||||||||||||||
| // dashfoundation.org is held by a third party). The Kotlin source packages stay | ||||||||||||||||||||||||||||||||||||
| // `org.dashfoundation.dashsdk`; this is a Maven-coordinates decision only. | ||||||||||||||||||||||||||||||||||||
| // `maven-publish` handles local / internal-repo publishing; jreleaser (bottom of | ||||||||||||||||||||||||||||||||||||
| // file) handles Maven Central releases and Sonatype SNAPSHOTs, mirroring | ||||||||||||||||||||||||||||||||||||
| // dashj/core/build.gradle. | ||||||||||||||||||||||||||||||||||||
| group = "org.dashj" | ||||||||||||||||||||||||||||||||||||
| version = project.findProperty("sdkVersion")?.toString() ?: "0.1.0-SNAPSHOT" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| android { | ||||||||||||||||||||||||||||||||||||
| namespace = "org.dashfoundation.dashsdk" | ||||||||||||||||||||||||||||||||||||
| compileSdk = 35 | ||||||||||||||||||||||||||||||||||||
|
|
@@ -35,6 +55,18 @@ android { | |||||||||||||||||||||||||||||||||||
| // libdash_sdk_jni.so is produced by ../build_android.sh (cargo-ndk) into | ||||||||||||||||||||||||||||||||||||
| // src/main/jniLibs/<abi>/ — AGP packages it from there. NDK r28 emits | ||||||||||||||||||||||||||||||||||||
| // 16 KB-aligned ELF segments by default (Android 15+ requirement). | ||||||||||||||||||||||||||||||||||||
| publishing { | ||||||||||||||||||||||||||||||||||||
| singleVariant("release") { | ||||||||||||||||||||||||||||||||||||
| withSourcesJar() | ||||||||||||||||||||||||||||||||||||
| // Maven Central's Publisher Portal rejects any non-POM component that | ||||||||||||||||||||||||||||||||||||
| // lacks a javadoc jar, and the snapshot deployer sets | ||||||||||||||||||||||||||||||||||||
| // applyMavenCentralRules(true) which enforces it before upload | ||||||||||||||||||||||||||||||||||||
| // (dashpay/platform#4045). AGP emits a (Kotlin-appropriate) javadoc jar | ||||||||||||||||||||||||||||||||||||
| // here — no Dokka dependency required. | ||||||||||||||||||||||||||||||||||||
| withJavadocJar() | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+66
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 Nitpick: Generated javadoc jar will be effectively empty — comment overstates what AGP produces The module (packages/kotlin-sdk/sdk/src/main) contains zero .java source files — confirmed by search, it is pure Kotlin. AGP’s source: [sonnet5-general] |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+68
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Release publication is missing a javadoc jar — real Maven Central deploys will be rejected This delta turns on a real Maven Central / Sonatype deploy path (
Suggested change
source: ['sonnet5', 'codex']
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread. |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| packaging { | ||||||||||||||||||||||||||||||||||||
| jniLibs { | ||||||||||||||||||||||||||||||||||||
| useLegacyPackaging = false | ||||||||||||||||||||||||||||||||||||
|
|
@@ -59,7 +91,11 @@ ksp { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| dependencies { | ||||||||||||||||||||||||||||||||||||
| implementation(libs.kotlinx.coroutines.core) | ||||||||||||||||||||||||||||||||||||
| // `api`, not `implementation`: the public SDK surface returns coroutines | ||||||||||||||||||||||||||||||||||||
| // types (Room DAO `Flow<…>`, `PlatformWalletManager.pendingIdentityKeys: | ||||||||||||||||||||||||||||||||||||
| // StateFlow<…>`, etc.), so a consumer of the published coordinate needs | ||||||||||||||||||||||||||||||||||||
| // coroutines-core on its own compile classpath. | ||||||||||||||||||||||||||||||||||||
| api(libs.kotlinx.coroutines.core) | ||||||||||||||||||||||||||||||||||||
| implementation(libs.kotlinx.coroutines.android) | ||||||||||||||||||||||||||||||||||||
| implementation(libs.kotlinx.serialization.json) | ||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: kotlinx-serialization-json is runtime-only despite a public @serializable type
Suggested change
source: ['codex']
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread. |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -83,3 +119,226 @@ dependencies { | |||||||||||||||||||||||||||||||||||
| androidTestImplementation(libs.espresso.core) | ||||||||||||||||||||||||||||||||||||
| androidTestImplementation(libs.room.testing) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Publishes the release AAR (incl. libdash_sdk_jni.so if ../build_android.sh ran first) with a | ||||||||||||||||||||||||||||||||||||
| // POM carrying the Room/DataStore/Biometric api dependencies. | ||||||||||||||||||||||||||||||||||||
| // Local: ./gradlew :sdk:publishToMavenLocal Coordinates: org.dashj:dash-sdk-android:<version> | ||||||||||||||||||||||||||||||||||||
| // Remote: `:sdk:publishReleasePublicationToStagingRepository` stages the signed | ||||||||||||||||||||||||||||||||||||
| // artifacts into build/staging-deploy, then `:sdk:jreleaserDeploy` uploads | ||||||||||||||||||||||||||||||||||||
| // them to Maven Central (release) / Sonatype snapshots (see jreleaser block). | ||||||||||||||||||||||||||||||||||||
| // Override the version with -PsdkVersion=x.y.z (defaults to 0.1.0-SNAPSHOT). | ||||||||||||||||||||||||||||||||||||
| afterEvaluate { | ||||||||||||||||||||||||||||||||||||
| publishing { | ||||||||||||||||||||||||||||||||||||
| repositories { | ||||||||||||||||||||||||||||||||||||
| // Local staging dir that jreleaser reads from and uploads (below); it is | ||||||||||||||||||||||||||||||||||||
| // not itself a network repo, so staging never needs credentials. | ||||||||||||||||||||||||||||||||||||
| maven { | ||||||||||||||||||||||||||||||||||||
| name = "staging" | ||||||||||||||||||||||||||||||||||||
| url = uri(layout.buildDirectory.dir("staging-deploy")) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // The staging dir is reused across runs and maven-publish never removes | ||||||||||||||||||||||||||||||||||||
| // other versions' directories — a release deploy after an earlier | ||||||||||||||||||||||||||||||||||||
| // snapshot/release staging run would hand JReleaser stale coordinates. | ||||||||||||||||||||||||||||||||||||
| // Wipe it before every staging publish so each deploy starts clean. | ||||||||||||||||||||||||||||||||||||
| val cleanStagingDeploy = tasks.register<Delete>("cleanStagingDeploy") { | ||||||||||||||||||||||||||||||||||||
| delete(layout.buildDirectory.dir("staging-deploy")) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| tasks.withType<PublishToMavenRepository>().configureEach { | ||||||||||||||||||||||||||||||||||||
| if (name.endsWith("ToStagingRepository")) { | ||||||||||||||||||||||||||||||||||||
| dependsOn(cleanStagingDeploy) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| publications { | ||||||||||||||||||||||||||||||||||||
| create<MavenPublication>("release") { | ||||||||||||||||||||||||||||||||||||
| from(components["release"]) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+154
to
+155
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Publishing can create an AAR without the JNI library The release publication is created directly from the Android release component, but there is no Gradle task dependency or validation that builds source: ['codex']
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indeed, we will need to integrate building of the JNI libs into the gradle build system. It can be done by running the build_android.sh script from the grade script or using CMake. There could be other better ways.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread. |
||||||||||||||||||||||||||||||||||||
| groupId = "org.dashj" | ||||||||||||||||||||||||||||||||||||
| artifactId = "dash-sdk-android" | ||||||||||||||||||||||||||||||||||||
| pom { | ||||||||||||||||||||||||||||||||||||
| name.set("Dash Platform Kotlin SDK") | ||||||||||||||||||||||||||||||||||||
| description.set( | ||||||||||||||||||||||||||||||||||||
| "Kotlin SDK for Dash Core (L1 SPV) and Dash Platform " + | ||||||||||||||||||||||||||||||||||||
| "(identities, DPNS, DashPay, shielded balances)" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| url.set("https://github.com/dashpay/platform") | ||||||||||||||||||||||||||||||||||||
| licenses { | ||||||||||||||||||||||||||||||||||||
| license { | ||||||||||||||||||||||||||||||||||||
| name.set("MIT License") | ||||||||||||||||||||||||||||||||||||
| url.set("https://github.com/dashpay/platform/blob/master/LICENSE") | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| // scm + developers: required by Maven Central (and most | ||||||||||||||||||||||||||||||||||||
| // remote repos) for POM validation. Pointed at the GitHub repo. | ||||||||||||||||||||||||||||||||||||
| scm { | ||||||||||||||||||||||||||||||||||||
| url.set("https://github.com/dashpay/platform") | ||||||||||||||||||||||||||||||||||||
| connection.set("scm:git:git://github.com/dashpay/platform.git") | ||||||||||||||||||||||||||||||||||||
| developerConnection.set( | ||||||||||||||||||||||||||||||||||||
| "scm:git:ssh://git@github.com/dashpay/platform.git" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| developers { | ||||||||||||||||||||||||||||||||||||
| developer { | ||||||||||||||||||||||||||||||||||||
| id.set("dashpay") | ||||||||||||||||||||||||||||||||||||
| name.set("Dash Platform Contributors") | ||||||||||||||||||||||||||||||||||||
| url.set("https://github.com/dashpay/platform") | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Sign published artifacts, but only for a real remote publish. `publishToMavenLocal` | ||||||||||||||||||||||||||||||||||||
| // and a bare `assemble` never put a PublishToMavenRepository task in the graph, so a | ||||||||||||||||||||||||||||||||||||
| // local build requires no GPG key. Keys come from the standard signing properties/env | ||||||||||||||||||||||||||||||||||||
| // (signing.keyId + signing.password + signing.secretKeyRingFile, or the in-memory | ||||||||||||||||||||||||||||||||||||
| // ORG_GRADLE_PROJECT_signingKey / signingPassword pair) when a remote publish runs. | ||||||||||||||||||||||||||||||||||||
| signing { | ||||||||||||||||||||||||||||||||||||
| // Wire the documented in-memory-key path: the ORG_GRADLE_PROJECT_signingKey | ||||||||||||||||||||||||||||||||||||
| // / signingPassword env vars land as the Gradle project properties | ||||||||||||||||||||||||||||||||||||
| // `signingKey` / `signingPassword`, but the signing plugin does not consume | ||||||||||||||||||||||||||||||||||||
| // them automatically — a CI release following that path would otherwise fail | ||||||||||||||||||||||||||||||||||||
| // in signReleasePublication before staging anything (dashpay/platform#4045). | ||||||||||||||||||||||||||||||||||||
| // Absent (local dev), this is skipped and, with required=false below, no key | ||||||||||||||||||||||||||||||||||||
| // is needed. The legacy signing.keyId/password/secretKeyRingFile path is | ||||||||||||||||||||||||||||||||||||
| // untouched and still handled by Gradle's standard signing properties. | ||||||||||||||||||||||||||||||||||||
| val signingKey = project.findProperty("signingKey") as String? | ||||||||||||||||||||||||||||||||||||
| val signingPassword = project.findProperty("signingPassword") as String? | ||||||||||||||||||||||||||||||||||||
| if (!signingKey.isNullOrBlank()) { | ||||||||||||||||||||||||||||||||||||
| useInMemoryPgpKeys(signingKey, signingPassword) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| setRequired(Callable { | ||||||||||||||||||||||||||||||||||||
| gradle.taskGraph.allTasks.any { it is PublishToMavenRepository } | ||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||
| sign(publishing.publications["release"]) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+197
to
+215
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Documented in-memory signing env vars are never wired to a signatory The comment directly above this block (lines 173-177) says signing keys can come from either the legacy
Suggested change
source: ['codex']
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread. |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Never publish a coordinate whose AAR lacks the native JNI library | ||||||||||||||||||||||||||||||||||||
| // (libdash_sdk_jni.so). ../build_android.sh (cargo-ndk) produces it into | ||||||||||||||||||||||||||||||||||||
| // src/main/jniLibs/<abi>/; that directory is gitignored and absent in a clean | ||||||||||||||||||||||||||||||||||||
| // checkout, so without this guard a publish from a fresh tree yields an artifact | ||||||||||||||||||||||||||||||||||||
| // that installs cleanly but dies at runtime with UnsatisfiedLinkError | ||||||||||||||||||||||||||||||||||||
| // (NativeLoader.ensureLoaded → System.loadLibrary("dash_sdk_jni")). | ||||||||||||||||||||||||||||||||||||
| val requiredJniAbis = listOf("arm64-v8a", "x86_64") // build_android.sh ABI policy | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| fun missingJniAbis(): List<String> = requiredJniAbis.filterNot { abi -> | ||||||||||||||||||||||||||||||||||||
| file("src/main/jniLibs/$abi/libdash_sdk_jni.so").isFile | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| fun jniMissingMessage(missing: List<String>): String = | ||||||||||||||||||||||||||||||||||||
| "Native JNI library libdash_sdk_jni.so is missing for ABI(s) " + | ||||||||||||||||||||||||||||||||||||
| "${missing.joinToString()} under src/main/jniLibs/ — run ./build_android.sh " + | ||||||||||||||||||||||||||||||||||||
| "before publishing so the coordinate isn't broken at runtime." | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Remote / staging publish: ALWAYS hard-fail on a missing .so. `-PallowMissingJni` | ||||||||||||||||||||||||||||||||||||
| // is deliberately NOT honored here — a nativeless AAR must never reach a network | ||||||||||||||||||||||||||||||||||||
| // repo, nor jreleaser's build/staging-deploy dir (which `jreleaserDeploy` then | ||||||||||||||||||||||||||||||||||||
| // uploads to Maven Central). This closes the escape where `-PallowMissingJni` | ||||||||||||||||||||||||||||||||||||
| // previously downgraded the check to a warning for every publish task | ||||||||||||||||||||||||||||||||||||
| // (dashpay/platform#4045, finding 8b899bc2e5e8). | ||||||||||||||||||||||||||||||||||||
| val verifyJniLibsForRemotePublish = tasks.register("verifyJniLibsForRemotePublish") { | ||||||||||||||||||||||||||||||||||||
| doLast { | ||||||||||||||||||||||||||||||||||||
| val missing = missingJniAbis() | ||||||||||||||||||||||||||||||||||||
| if (missing.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||
| throw GradleException( | ||||||||||||||||||||||||||||||||||||
| jniMissingMessage(missing) + | ||||||||||||||||||||||||||||||||||||
| " -PallowMissingJni is a local-only escape and is NOT honored for " + | ||||||||||||||||||||||||||||||||||||
| "remote/staging deploys." | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Local publish (`publishToMavenLocal`): a dev may pass `-PallowMissingJni` for a | ||||||||||||||||||||||||||||||||||||
| // POM/metadata-only dry run into the local ~/.m2, which never leaves the machine. | ||||||||||||||||||||||||||||||||||||
| val verifyJniLibsForLocalPublish = tasks.register("verifyJniLibsForLocalPublish") { | ||||||||||||||||||||||||||||||||||||
| doLast { | ||||||||||||||||||||||||||||||||||||
| val missing = missingJniAbis() | ||||||||||||||||||||||||||||||||||||
| if (missing.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||
| val message = jniMissingMessage(missing) | ||||||||||||||||||||||||||||||||||||
| if (project.hasProperty("allowMissingJni")) { | ||||||||||||||||||||||||||||||||||||
| logger.warn( | ||||||||||||||||||||||||||||||||||||
| "WARNING: $message (continuing: -PallowMissingJni is set — local publish only.)" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| throw GradleException("$message Pass -PallowMissingJni for a local-only dry run.") | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| tasks.withType<PublishToMavenRepository>().configureEach { | ||||||||||||||||||||||||||||||||||||
| dependsOn(verifyJniLibsForRemotePublish) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| tasks.withType<PublishToMavenLocal>().configureEach { | ||||||||||||||||||||||||||||||||||||
| dependsOn(verifyJniLibsForLocalPublish) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| // jreleaser's deploy/upload/release tasks push the already-staged artifacts, so | ||||||||||||||||||||||||||||||||||||
| // gate them on the strict remote check too — a staging dir populated out-of-band | ||||||||||||||||||||||||||||||||||||
| // (or by a future task rewiring) can't be uploaded without the native library. | ||||||||||||||||||||||||||||||||||||
| tasks.matching { task -> | ||||||||||||||||||||||||||||||||||||
| task.name.startsWith("jreleaser") && | ||||||||||||||||||||||||||||||||||||
| listOf("Deploy", "Upload", "Release").any { task.name.contains(it) } | ||||||||||||||||||||||||||||||||||||
| }.configureEach { dependsOn(verifyJniLibsForRemotePublish) } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Maven Central / Sonatype deployment via jreleaser, mirroring | ||||||||||||||||||||||||||||||||||||
| // dashj/core/build.gradle (https://github.com/dashpay/dashj/blob/master/core/build.gradle#L139). | ||||||||||||||||||||||||||||||||||||
| // Flow: `maven-publish` stages the signed artifacts into build/staging-deploy (the | ||||||||||||||||||||||||||||||||||||
| // `staging` repository configured above), then `./gradlew :sdk:jreleaserDeploy` | ||||||||||||||||||||||||||||||||||||
| // uploads them — release versions to Maven Central, -SNAPSHOT versions to the | ||||||||||||||||||||||||||||||||||||
| // Sonatype snapshots repo. jreleaser reads its GPG key and Sonatype credentials | ||||||||||||||||||||||||||||||||||||
| // from env/props (JRELEASER_GPG_*, JRELEASER_MAVENCENTRAL_*/JRELEASER_NEXUS2_*) | ||||||||||||||||||||||||||||||||||||
| // at deploy time only, so `publishToMavenLocal` and any build that never invokes a | ||||||||||||||||||||||||||||||||||||
| // jreleaser task need no signing key or account. | ||||||||||||||||||||||||||||||||||||
| jreleaser { | ||||||||||||||||||||||||||||||||||||
| project { | ||||||||||||||||||||||||||||||||||||
| name.set("dash-sdk-android") | ||||||||||||||||||||||||||||||||||||
| description.set( | ||||||||||||||||||||||||||||||||||||
| "Kotlin SDK for Dash Core (L1 SPV) and Dash Platform " + | ||||||||||||||||||||||||||||||||||||
| "(identities, DPNS, DashPay, shielded balances)" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| links { | ||||||||||||||||||||||||||||||||||||
| homepage.set("https://github.com/dashpay/platform") | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| authors.set(listOf("Dash Platform Contributors")) | ||||||||||||||||||||||||||||||||||||
| license.set("MIT") | ||||||||||||||||||||||||||||||||||||
| gitRootSearch.set(true) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| signing { | ||||||||||||||||||||||||||||||||||||
| active.set(Active.ALWAYS) | ||||||||||||||||||||||||||||||||||||
| armored.set(true) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| deploy { | ||||||||||||||||||||||||||||||||||||
| maven { | ||||||||||||||||||||||||||||||||||||
| // Tagged release versions -> Maven Central via the Sonatype portal. | ||||||||||||||||||||||||||||||||||||
| mavenCentral { | ||||||||||||||||||||||||||||||||||||
| create("sonatype") { | ||||||||||||||||||||||||||||||||||||
| active.set(Active.RELEASE) | ||||||||||||||||||||||||||||||||||||
| url.set("https://central.sonatype.com/api/v1/publisher") | ||||||||||||||||||||||||||||||||||||
| stagingRepository( | ||||||||||||||||||||||||||||||||||||
| layout.buildDirectory.dir("staging-deploy").get().asFile.path | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| // -SNAPSHOT versions -> Sonatype snapshots repository. | ||||||||||||||||||||||||||||||||||||
| nexus2 { | ||||||||||||||||||||||||||||||||||||
| create("snapshots") { | ||||||||||||||||||||||||||||||||||||
| active.set(Active.SNAPSHOT) | ||||||||||||||||||||||||||||||||||||
| url.set("https://central.sonatype.com/repository/maven-snapshots/") | ||||||||||||||||||||||||||||||||||||
| snapshotUrl.set("https://central.sonatype.com/repository/maven-snapshots/") | ||||||||||||||||||||||||||||||||||||
| applyMavenCentralRules.set(true) | ||||||||||||||||||||||||||||||||||||
| snapshotSupported.set(true) | ||||||||||||||||||||||||||||||||||||
| closeRepository.set(true) | ||||||||||||||||||||||||||||||||||||
| releaseRepository.set(true) | ||||||||||||||||||||||||||||||||||||
| stagingRepository( | ||||||||||||||||||||||||||||||||||||
| layout.buildDirectory.dir("staging-deploy").get().asFile.path | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Blocking: Clean the staging repository before assembling a release
The runbook publishes each requested version into the fixed
sdk/build/staging-deployMaven repository and then gives that entire directory to both configured JReleaser deployers. Maven publication adds or replaces files for the current coordinates but does not remove other version directories, and JReleaser explicitly recommends deploying from a clean state. Reusing this sequence after an earlier snapshot or release staging run can therefore bundle stale versions with the intended release, causing duplicate-version or validation failures and potentially uploading unintended artifacts. Run:sdk:cleanbefore staging; it removes the configured build directory while preserving the JNI libraries undersrc/main/jniLibs.source: ['codex']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the latest push: a
cleanStagingDeployDelete task now wipessdk/build/staging-deploybefore every*ToStagingRepositorypublish (wired viatasks.withType<PublishToMavenRepository>), so JReleaser always deploys from a clean staging state regardless of prior snapshot/release runs. Verified in the task graph with--dry-run; PUBLISHING.md updated to note the automatic wipe.