Skip to content
Draft
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
143 changes: 143 additions & 0 deletions .github/workflows/maven-central-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Publish to Maven Central

on:
# TODO: remove after testing
push:
branches: [ci/kotlin-release]
workflow_dispatch:
inputs:
version:
description: "Version to publish (leave empty for release version)"
required: false
type: string
dry_run:
description: "Dry run - publish to local Maven repo only"
required: false
default: true
type: boolean

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
lib_ext: so
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
lib_ext: so
- os: macos-latest
target: x86_64-apple-darwin
lib_ext: dylib
- os: macos-latest
target: aarch64-apple-darwin
lib_ext: dylib
- os: windows-latest
target: x86_64-pc-windows-msvc
lib_ext: dll
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- name: Build the bindings
run: make kotlin

- name: Upload native library
uses: actions/upload-artifact@v4
with:
name: native-lib-${{ matrix.os }}-${{ matrix.target }}
path: bindings/kotlin/lib/*iota_sdk_ffi*

publish:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

# - name: Build Rust library
# run: cargo build -p iota-sdk-ffi --lib --release

- name: Download native libraries
uses: actions/download-artifact@v4
with:
path: libs

- name: Prepare libraries
run: |
echo "Listing contents of libs directory:"
ls -la libs/
echo "Listing contents of native-lib-ubuntu-latest-x86_64-unknown-linux-gnu:"
ls -la libs/native-lib-ubuntu-latest-x86_64-unknown-linux-gnu/
echo "Listing contents of bindings/kotlin/lib before copying:"
ls -la bindings/kotlin/lib/
cd bindings/kotlin/lib
echo "Copying libraries..."
cp ../../../libs/native-lib-ubuntu-latest-x86_64-unknown-linux-gnu/libiota_sdk_ffi.so .
cp ../../../libs/native-lib-ubuntu-latest-aarch64-unknown-linux-gnu/libiota_sdk_ffi.so libiota_sdk_ffi_arm64.so
cp ../../../libs/native-lib-macos-latest-x86_64-apple-darwin/libiota_sdk_ffi.dylib .
cp ../../../libs/native-lib-macos-latest-aarch64-apple-darwin/libiota_sdk_ffi.dylib libiota_sdk_ffi_arm64.dylib
cp ../../../libs/native-lib-windows-latest-x86_64-pc-windows-msvc/iota_sdk_ffi.dll .
echo "Contents after copying:"
ls -la

- name: Generate Kotlin bindings
run: |
cd bindings/kotlin
# Generate bindings using the Linux library (arbitrary choice)
cargo run --bin iota_sdk_bindings -- generate --library "lib/libiota_sdk_ffi.so" --language kotlin --out-dir lib --no-format -c uniffi.toml

- name: Set version for release
if: github.event_name == 'release'
run: |
cd bindings/kotlin
sed -i "s/version = .*/version = \"${{ github.event.release.tag_name }}\"/" build.gradle.kts

- name: Set version for manual dispatch
if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
run: |
cd bindings/kotlin
sed -i "s/version = .*/version = \"${{ github.event.inputs.version }}\"/" build.gradle.kts

- name: Publish to Maven Central
if: github.event_name == 'release' || github.event.inputs.dry_run != 'true'
run: |
cd bindings/kotlin
./gradlew publish --no-daemon --no-parallel
env:
ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }}

- name: Dry run - Local publish only
if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true'
run: |
cd bindings/kotlin
echo "Dry run: Publishing to local Maven repository only"
./gradlew publishToMavenLocal --no-daemon --no-parallel
env:
ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }}
22 changes: 14 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ bindings-example: ## Run a specific example for all bindings. Usage: make bindin
define build_binding
cargo build -p iota-sdk-ffi --lib --release; \
case "$$(uname -s)" in \
Darwin) LIB_EXT=".dylib" ;; \
Linux) LIB_EXT=".so" ;; \
MINGW*|MSYS*|CYGWIN*|Windows_NT) LIB_EXT=".dll" ;; \
Darwin) LIB_PREFIX="lib"; LIB_EXT=".dylib" ;; \
Linux) LIB_PREFIX="lib"; LIB_EXT=".so" ;; \
MINGW*|MSYS*|CYGWIN*|Windows_NT) LIB_PREFIX=""; LIB_EXT=".dll" ;; \
*) echo "Unsupported platform"; exit 1 ;; \
esac;
endef
Expand All @@ -97,21 +97,27 @@ endef
go: ## Build Go bindings
@printf "Building Go bindings...\n"
@$(build_binding) \
uniffi-bindgen-go --library target/release/libiota_sdk_ffi$${LIB_EXT} --out-dir bindings/go --no-format || exit $$?
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
uniffi-bindgen-go --library target/release/$${LIB_NAME} --out-dir bindings/go --no-format || exit $$?

.PHONY: kotlin
kotlin: ## Build Kotlin bindings
@printf "Building Kotlin bindings...\n"
@$(build_binding) \
cargo run --bin iota_sdk_bindings -- generate --library "target/release/libiota_sdk_ffi$${LIB_EXT}" --language kotlin --out-dir bindings/kotlin/lib --no-format -c bindings/kotlin/uniffi.toml || exit $$?; \
cp target/release/libiota_sdk_ffi$${LIB_EXT} bindings/kotlin/lib/
printf "Built library with LIB_PREFIX=$${LIB_PREFIX}, LIB_EXT=$${LIB_EXT}\n"; \
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
printf "Checking if library exists: target/release/$${LIB_NAME}\n"; \
test -f "target/release/$${LIB_NAME}" || (echo "Library not found!" && exit 1); \
cargo run --bin iota_sdk_bindings -- generate --library "target/release/$${LIB_NAME}" --language kotlin --out-dir bindings/kotlin/lib --no-format -c bindings/kotlin/uniffi.toml || exit $$?; \
cp target/release/$${LIB_NAME} bindings/kotlin/lib/

.PHONY: python
python: ## Build Python bindings
@printf "Building Python bindings...\n"
@$(build_binding) \
cargo run --bin iota_sdk_bindings -- generate --library "target/release/libiota_sdk_ffi$${LIB_EXT}" --language python --out-dir bindings/python/lib --no-format || exit $$?; \
cp target/release/libiota_sdk_ffi$${LIB_EXT} bindings/python/lib/
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
cargo run --bin iota_sdk_bindings -- generate --library "target/release/$${LIB_NAME}" --language python --out-dir bindings/python/lib --no-format || exit $$?; \
cp target/release/$${LIB_NAME} bindings/python/lib/

.PHONY: go-example
go-example: ## Run a specific Go example. Usage: make go-example example
Expand Down
43 changes: 43 additions & 0 deletions bindings/kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,46 @@ make kotlin
```sh
make kotlin-example chain_id
```

## Publishing to Maven Central

### Publishing

For snapshot releases, set the version in `build.gradle.kts` to end with `-SNAPSHOT`.

#### Dry Run Testing

To test the publishing process without actually publishing to Maven Central:

1. Go to the Actions tab in GitHub
2. Select "Publish to Maven Central" workflow
3. Click "Run workflow"
4. Check "Dry run - publish to local Maven repo only"
5. Optionally specify a version
6. Run the workflow

This will build all artifacts, sign them, and publish to your local Maven repository (`~/.m2/repository`) for verification, without uploading to Maven Central.

**Note:** The dry run uses the same GPG signing secrets as real publishing, but skips the Sonatype upload step.

#### Local Testing

You can also test the publishing process locally (signing is optional):

**Complete local testing script:**

```bash
#!/bin/bash
cd bindings/kotlin

# Test publish to Maven Local (no GPG required)
./gradlew clean publishToMavenLocal --info

# Verify the results
echo "Published artifacts:"
find ~/.m2/repository/org/iota -name "*.jar" -o -name "*.pom" | head -5

echo "JAR contents check:"
jar -tf ~/.m2/repository/org/iota/iota-sdk-jvm/1.0-SNAPSHOT/iota-sdk-jvm-1.0-SNAPSHOT.jar | grep -E "(libiota_sdk_ffi|iota_sdk)" | wc -l
echo "files found (should be > 1)"
```
91 changes: 81 additions & 10 deletions bindings/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ plugins {
kotlin("jvm") version "1.9.24"
kotlin("plugin.serialization") version "1.9.24"
application
`maven-publish`
signing
}

import java.util.Base64

group = "org.iota"

version = "1.0-SNAPSHOT"
Expand All @@ -22,19 +26,22 @@ kotlin { jvmToolchain(21) }
tasks.register<JavaExec>("example") {
classpath = sourceSets["main"].runtimeClasspath
jvmArgs = listOf("-Djna.library.path=${projectDir}/lib")

// Get the example name from the command line argument -Pexample=<name>
val exampleProperty = "example"
inputs.property(exampleProperty, project.findProperty(exampleProperty) ?: "example")

mainClass.set(provider {
val example = project.findProperty(exampleProperty)?.toString() ?: "example"
// Convert snake_case to CamelCase and append Kt
val className = example.split('_')
.map { it.replaceFirstChar { c -> c.uppercaseChar() } }
.joinToString("")
"${className}Kt"
})

mainClass.set(
provider {
val example = project.findProperty(exampleProperty)?.toString() ?: "example"
// Convert snake_case to CamelCase and append Kt
val className =
example.split('_')
.map { it.replaceFirstChar { c -> c.uppercaseChar() } }
.joinToString("")
"${className}Kt"
}
)
}

sourceSets {
Expand Down Expand Up @@ -80,3 +87,67 @@ tasks.register("compileWithErrors") {
}
}
}

publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
pom {
name.set("IOTA SDK Kotlin Bindings")
description.set("Kotlin bindings for the IOTA SDK")
url.set("https://github.com/iotaledger/iota-rust-sdk")
licenses {
license {
name.set("Apache-2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("iotaledger")
name.set("IOTA Foundation")
email.set("[email protected]")
}
}
scm {
connection.set("scm:git:git://github.com/iotaledger/iota-rust-sdk.git")
developerConnection.set("scm:git:ssh://github.com/iotaledger/iota-rust-sdk.git")
url.set("https://github.com/iotaledger/iota-rust-sdk")
}
}
}
}
repositories {
maven {
name = "ossrh"
url = uri(
if (version.toString().endsWith("SNAPSHOT")) {
"https://s01.oss.sonatype.org/content/repositories/snapshots/"
} else {
"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
}
)
val sonatypeUsername = providers.environmentVariable("ORG_GRADLE_PROJECT_SONATYPE_USERNAME")
val sonatypePassword = providers.environmentVariable("ORG_GRADLE_PROJECT_SONATYPE_PASSWORD")
if (sonatypeUsername.isPresent && sonatypePassword.isPresent) {
credentials {
username = sonatypeUsername.get()
password = sonatypePassword.get()
}
}
}
}
}

signing {
val signingKeyEncoded = providers.environmentVariable("ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY")
val signingPassword = providers.environmentVariable("ORG_GRADLE_PROJECT_SIGNING_PASSWORD")
if (signingKeyEncoded.isPresent && signingPassword.isPresent) {
val signingKey = String(Base64.getDecoder().decode(signingKeyEncoded.get()))
useInMemoryPgpKeys(
signingPassword.get(),
signingKey
)
sign(publishing.publications["maven"])
}
}
Loading