Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,41 @@ jobs:
- name: Build Kotlin sample app
working-directory: kotlin/Examples/IDKitSampleApp
run: ./gradlew :app:assembleDebug

- name: Validate Kotlin Maven publication
run: |
set -euo pipefail

./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin \
-Pidkit.publish.mavenCentral=true \
:bindings:publishToMavenCentral --dry-run

VERSION="$(grep '^version=' kotlin/gradle.properties | cut -d= -f2- | tr -d '[:space:]')"
ARTIFACT_DIR="$HOME/.m2/repository/com/worldcoin/idkit/$VERSION"
ARTIFACT_BASE="$ARTIFACT_DIR/idkit-$VERSION"

for artifact in \
"$ARTIFACT_BASE.aar" \
"$ARTIFACT_BASE.pom" \
"$ARTIFACT_BASE.module" \
"$ARTIFACT_BASE-sources.jar" \
"$ARTIFACT_BASE-javadoc.jar"; do
if [ ! -s "$artifact" ]; then
echo "::error::Missing Maven publication artifact: $artifact"
exit 1
fi
done

grep -q '<groupId>com.worldcoin</groupId>' "$ARTIFACT_BASE.pom"
grep -q '<artifactId>idkit</artifactId>' "$ARTIFACT_BASE.pom"
grep -q '<packaging>aar</packaging>' "$ARTIFACT_BASE.pom"

AAR_CONTENTS="$(mktemp)"
jar tf "$ARTIFACT_BASE.aar" > "$AAR_CONTENTS"
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
if ! grep -q "^jni/$abi/libidkit.so$" "$AAR_CONTENTS"; then
echo "::error::Missing native library in AAR: jni/$abi/libidkit.so"
exit 1
fi
done
2 changes: 1 addition & 1 deletion kotlin/Examples/IDKitSampleApp/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {

defaultConfig {
applicationId = "com.worldcoin.idkit.sample"
minSdk = 26
minSdk = 23
targetSdk = 35
versionCode = 1
versionName = "1.0"
Expand Down
65 changes: 65 additions & 0 deletions kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Kotlin SDK for World ID verification, backed by the Rust core via UniFFI.
## Installation

The Kotlin package is published to GitHub Packages as `com.worldcoin:idkit`.
The same publication is also prepared for `mavenLocal()` and Maven Central. Maven Central upload is available as an explicit local opt-in, but it is intentionally not wired into GitHub release workflows until the required secrets are available there and the workflow has been tested end to end.

GitHub Packages requires authentication for Maven downloads, even for public packages.
Create a token with `read:packages` and expose it through environment variables.
Expand All @@ -24,6 +25,25 @@ dependencyResolutionManagement {
}
```

For local integration testing, build the Kotlin artifacts, publish them to `mavenLocal()`, and add `mavenLocal()` to the consuming app repositories:

```bash
bash scripts/build-kotlin.sh
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
```

Then add `mavenLocal()` to the consuming app repositories:

```kotlin
dependencyResolutionManagement {
repositories {
mavenLocal()
google()
mavenCentral()
}
}
```

Then add the dependency:

```kotlin
Expand Down Expand Up @@ -190,8 +210,53 @@ If Gradle is available locally:

```bash
gradle -p kotlin bindings:test
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
```

## Publishing

The existing Kotlin release workflow publishes to GitHub Packages. That path is still active and uses GitHub's package credentials:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publish
```

Without `-Pidkit.publish.mavenCentral=true`, this does not configure Maven Central upload or signing tasks.

For local integration testing, publish the same artifact to the local Maven repository:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
```

To publish to Maven Central from a local machine that already has credentials, keep the secrets in `~/.gradle/gradle.properties`:

```properties
mavenCentralUsername=<central-portal-token-username>
mavenCentralPassword=<central-portal-token-password>
signing.keyId=<gpg-key-id>
signing.password=<gpg-key-password>
signing.secretKeyRingFile=/path/to/secring.gpg
```

Then explicitly enable the Central publishing path for that Gradle invocation:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin \
-Pidkit.publish.mavenCentral=true \
:bindings:publishToMavenCentral
```

To upload and release from the Central Portal deployment in one command, run:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin \
-Pidkit.publish.mavenCentral=true \
:bindings:publishAndReleaseToMavenCentral
```

The automated release workflow continues publishing Kotlin artifacts to GitHub Packages, but it does not publish to Maven Central yet. Add Maven Central release-workflow steps only after the required credentials and end-to-end release path are ready.

## Troubleshooting

- `connection_failed`:
Expand Down
160 changes: 111 additions & 49 deletions kotlin/bindings/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
import com.vanniktech.maven.publish.AndroidSingleVariantLibrary
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
import org.gradle.jvm.tasks.Jar

plugins {
id("com.android.library")
kotlin("android")
`maven-publish`
id("com.vanniktech.maven.publish.base") version "0.34.0"
}

group = "com.worldcoin"
val libraryGroup = "com.worldcoin"
val libraryArtifactId = "idkit"

// Support version override from CI for dev releases
version = System.getenv("PKG_VERSION")?.takeIf { it.isNotBlank() }
// Allow callers to exercise the Maven publication with an explicit artifact version.
val libraryVersion = System.getenv("PKG_VERSION")?.takeIf { it.isNotBlank() }
?: project.version.toString().takeIf { it.isNotBlank() && it != "unspecified" }
?: throw GradleException("Could not find version in kotlin/gradle.properties")

val enableMavenCentralPublishing = providers.gradleProperty("idkit.publish.mavenCentral")
.map(String::toBoolean)
.orElse(false)

val emptyJavadocJar by tasks.registering(Jar::class) {
archiveClassifier.set("javadoc")
}

val requiredNativeAbis = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
val verifyKotlinNativeLibraries by tasks.registering {
group = "verification"
description = "Verifies that Kotlin publishing includes native IDKit libraries for every Android ABI."

doLast {
val missingLibraries = requiredNativeAbis.map { abi ->
abi to layout.projectDirectory.file("src/main/jniLibs/$abi/libidkit.so").asFile
}.filter { (_, library) ->
!library.isFile || library.length() == 0L
}

if (missingLibraries.isNotEmpty()) {
val missing = missingLibraries.joinToString(separator = "\n") { (abi, library) ->
"- $abi: ${library.relativeTo(projectDir)}"
}
throw GradleException(
"Missing native libraries required for publishing:\n$missing\n" +
"Run `bash scripts/build-kotlin.sh` from the repository root before publishing.",
)
}
}
}

group = libraryGroup
version = libraryVersion

android {
namespace = "com.worldcoin.idkit"
compileSdk = 35

defaultConfig {
minSdk = 26
minSdk = 23
}

compileOptions {
Expand All @@ -28,12 +70,6 @@ android {
jvmTarget = "17"
}

publishing {
singleVariant("release") {
withSourcesJar()
}
}

testOptions {
unitTests.all { test ->
val rustLibDir = project.projectDir.resolve("../../target/release").canonicalPath
Expand All @@ -53,47 +89,73 @@ dependencies {
testImplementation("net.java.dev.jna:jna:5.14.0")
}

afterEvaluate {
publishing {
publications {
create<MavenPublication>("maven") {
from(components["release"])
groupId = "com.worldcoin"
artifactId = "idkit"
version = project.version.toString()
pom {
name.set("IDKit Kotlin")
description.set("Kotlin bindings for IDKit backed by the Rust core")
url.set("https://github.com/worldcoin/idkit")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("worldcoin")
name.set("Worldcoin")
}
}
scm {
connection.set("scm:git:https://github.com/worldcoin/idkit.git")
developerConnection.set("scm:git:ssh://git@github.com/worldcoin/idkit.git")
url.set("https://github.com/worldcoin/idkit")
}
}
mavenPublishing {
configure(
AndroidSingleVariantLibrary(
variant = "release",
sourcesJar = true,
publishJavadocJar = false,
),
)

coordinates(libraryGroup, libraryArtifactId, libraryVersion)

pom {
name.set("IDKit Kotlin")
description.set("Kotlin bindings for IDKit backed by the Rust core")
url.set("https://github.com/worldcoin/idkit")
licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("worldcoin")
name.set("Worldcoin")
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/worldcoin/idkit")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
scm {
connection.set("scm:git:https://github.com/worldcoin/idkit.git")
developerConnection.set("scm:git:ssh://git@github.com/worldcoin/idkit.git")
url.set("https://github.com/worldcoin/idkit")
}
}

if (enableMavenCentralPublishing.get()) {
publishToMavenCentral()
signAllPublications()
}
}

publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/worldcoin/idkit")
credentials {
username = providers.environmentVariable("GITHUB_ACTOR")
.orElse(providers.environmentVariable("GITHUB_USER"))
.orNull
password = providers.environmentVariable("GITHUB_TOKEN").orNull
}
}
}
}

tasks.withType<PublishToMavenRepository>().configureEach {
dependsOn(verifyKotlinNativeLibraries)
}

tasks.withType<PublishToMavenLocal>().configureEach {
dependsOn(verifyKotlinNativeLibraries)
}

afterEvaluate {
publishing {
publications.withType<MavenPublication>().configureEach {
artifact(emptyJavadocJar)
}
}
}
Loading