Skip to content

Commit a7def05

Browse files
committed
feat(kotlin): add publish workflow
1 parent e07def7 commit a7def05

File tree

4 files changed

+268
-8
lines changed

4 files changed

+268
-8
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
name: Publish to Maven Central
2+
3+
on:
4+
# TODO: remove after testing
5+
push:
6+
branches: [ci/kotlin-release]
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: "Version to publish (leave empty for release version)"
11+
required: false
12+
type: string
13+
dry_run:
14+
description: "Dry run - publish to local Maven repo only"
15+
required: false
16+
default: true
17+
type: boolean
18+
19+
jobs:
20+
build:
21+
runs-on: ${{ matrix.os }}
22+
strategy:
23+
matrix:
24+
include:
25+
- os: ubuntu-latest
26+
target: x86_64-unknown-linux-gnu
27+
lib_ext: so
28+
- os: ubuntu-latest
29+
target: aarch64-unknown-linux-gnu
30+
lib_ext: so
31+
- os: macos-latest
32+
target: x86_64-apple-darwin
33+
lib_ext: dylib
34+
- os: macos-latest
35+
target: aarch64-apple-darwin
36+
lib_ext: dylib
37+
- os: windows-latest
38+
target: x86_64-pc-windows-msvc
39+
lib_ext: dll
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v4
43+
44+
- name: Set up JDK 21
45+
uses: actions/setup-java@v4
46+
with:
47+
java-version: "21"
48+
distribution: "temurin"
49+
50+
- name: Install Rust
51+
uses: dtolnay/rust-toolchain@stable
52+
with:
53+
targets: ${{ matrix.target }}
54+
55+
- name: Build the bindings
56+
run: make kotlin
57+
58+
- name: Upload native library
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: native-lib-${{ matrix.os }}-${{ matrix.target }}
62+
path: bindings/kotlin/lib/*iota_sdk_ffi*
63+
64+
publish:
65+
runs-on: ubuntu-latest
66+
needs: build
67+
steps:
68+
- name: Checkout repository
69+
uses: actions/checkout@v4
70+
71+
- name: Set up JDK 21
72+
uses: actions/setup-java@v4
73+
with:
74+
java-version: "21"
75+
distribution: "temurin"
76+
77+
- name: Install Rust
78+
uses: dtolnay/rust-toolchain@stable
79+
80+
# - name: Build Rust library
81+
# run: cargo build -p iota-sdk-ffi --lib --release
82+
83+
- name: Download native libraries
84+
uses: actions/download-artifact@v4
85+
with:
86+
path: libs
87+
88+
- name: Prepare libraries
89+
run: |
90+
echo "Listing contents of libs directory:"
91+
ls -la libs/
92+
echo "Listing contents of native-lib-ubuntu-latest-x86_64-unknown-linux-gnu:"
93+
ls -la libs/native-lib-ubuntu-latest-x86_64-unknown-linux-gnu/
94+
echo "Listing contents of bindings/kotlin/lib before copying:"
95+
ls -la bindings/kotlin/lib/
96+
cd bindings/kotlin/lib
97+
echo "Copying libraries..."
98+
cp ../../../libs/native-lib-ubuntu-latest-x86_64-unknown-linux-gnu/libiota_sdk_ffi.so .
99+
cp ../../../libs/native-lib-ubuntu-latest-aarch64-unknown-linux-gnu/libiota_sdk_ffi.so libiota_sdk_ffi_arm64.so
100+
cp ../../../libs/native-lib-macos-latest-x86_64-apple-darwin/libiota_sdk_ffi.dylib .
101+
cp ../../../libs/native-lib-macos-latest-aarch64-apple-darwin/libiota_sdk_ffi.dylib libiota_sdk_ffi_arm64.dylib
102+
cp ../../../libs/native-lib-windows-latest-x86_64-pc-windows-msvc/iota_sdk_ffi.dll .
103+
echo "Contents after copying:"
104+
ls -la
105+
106+
- name: Generate Kotlin bindings
107+
run: |
108+
cd bindings/kotlin
109+
# Generate bindings using the Linux library (arbitrary choice)
110+
cargo run --bin iota_sdk_bindings -- generate --library "lib/libiota_sdk_ffi.so" --language kotlin --out-dir lib --no-format -c uniffi.toml
111+
112+
- name: Set version for release
113+
if: github.event_name == 'release'
114+
run: |
115+
cd bindings/kotlin
116+
sed -i "s/version = .*/version = \"${{ github.event.release.tag_name }}\"/" build.gradle.kts
117+
118+
- name: Set version for manual dispatch
119+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
120+
run: |
121+
cd bindings/kotlin
122+
sed -i "s/version = .*/version = \"${{ github.event.inputs.version }}\"/" build.gradle.kts
123+
124+
- name: Publish to Maven Central
125+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false')
126+
run: |
127+
cd bindings/kotlin
128+
./gradlew publish --no-daemon --no-parallel
129+
env:
130+
ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }}
131+
ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }}
132+
ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }}
133+
ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }}
134+
135+
- name: Dry run - Local publish only
136+
if: github.event_name != 'release' && (github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run == 'true')
137+
run: |
138+
cd bindings/kotlin
139+
echo "Dry run: Publishing to local Maven repository only"
140+
./gradlew publishToMavenLocal --no-daemon --no-parallel
141+
env:
142+
ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }}
143+
ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }}

Makefile

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ bindings-examples-format: ## Format all bindings examples
105105
define build_binding
106106
cargo build -p iota-sdk-ffi --lib --release; \
107107
case "$$(uname -s)" in \
108-
Darwin) LIB_EXT=".dylib" ;; \
109-
Linux) LIB_EXT=".so" ;; \
110-
MINGW*|MSYS*|CYGWIN*|Windows_NT) LIB_EXT=".dll" ;; \
108+
Darwin) LIB_PREFIX="lib"; LIB_EXT=".dylib" ;; \
109+
Linux) LIB_PREFIX="lib"; LIB_EXT=".so" ;; \
110+
MINGW*|MSYS*|CYGWIN*|Windows_NT) LIB_PREFIX=""; LIB_EXT=".dll" ;; \
111111
*) echo "Unsupported platform"; exit 1 ;; \
112112
esac;
113113
endef
@@ -116,21 +116,27 @@ endef
116116
go: ## Build Go bindings
117117
@printf "Building Go bindings...\n"
118118
@$(build_binding) \
119-
uniffi-bindgen-go --library target/release/libiota_sdk_ffi$${LIB_EXT} --out-dir bindings/go --no-format || exit $$?
119+
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
120+
uniffi-bindgen-go --library target/release/$${LIB_NAME} --out-dir bindings/go --no-format || exit $$?
120121

121122
.PHONY: kotlin
122123
kotlin: ## Build Kotlin bindings
123124
@printf "Building Kotlin bindings...\n"
124125
@$(build_binding) \
125-
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 $$?; \
126-
cp target/release/libiota_sdk_ffi$${LIB_EXT} bindings/kotlin/lib/
126+
printf "Built library with LIB_PREFIX=$${LIB_PREFIX}, LIB_EXT=$${LIB_EXT}\n"; \
127+
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
128+
printf "Checking if library exists: target/release/$${LIB_NAME}\n"; \
129+
test -f "target/release/$${LIB_NAME}" || (echo "Library not found!" && exit 1); \
130+
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 $$?; \
131+
cp target/release/$${LIB_NAME} bindings/kotlin/lib/
127132

128133
.PHONY: python
129134
python: ## Build Python bindings
130135
@printf "Building Python bindings...\n"
131136
@$(build_binding) \
132-
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 $$?; \
133-
cp target/release/libiota_sdk_ffi$${LIB_EXT} bindings/python/lib/
137+
LIB_NAME="$${LIB_PREFIX}iota_sdk_ffi$${LIB_EXT}"; \
138+
cargo run --bin iota_sdk_bindings -- generate --library "target/release/$${LIB_NAME}" --language python --out-dir bindings/python/lib --no-format || exit $$?; \
139+
cp target/release/$${LIB_NAME} bindings/python/lib/
134140

135141
.PHONY: go-example
136142
go-example: ## Run a specific Go example. Usage: make go-example example

bindings/kotlin/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,46 @@ make kotlin
2828
```sh
2929
make kotlin-example chain_id
3030
```
31+
32+
## Publishing to Maven Central
33+
34+
### Publishing
35+
36+
For snapshot releases, set the version in `build.gradle.kts` to end with `-SNAPSHOT`.
37+
38+
#### Dry Run Testing
39+
40+
To test the publishing process without actually publishing to Maven Central:
41+
42+
1. Go to the Actions tab in GitHub
43+
2. Select "Publish to Maven Central" workflow
44+
3. Click "Run workflow"
45+
4. Check "Dry run - publish to local Maven repo only"
46+
5. Optionally specify a version
47+
6. Run the workflow
48+
49+
This will build all artifacts, sign them, and publish to your local Maven repository (`~/.m2/repository`) for verification, without uploading to Maven Central.
50+
51+
**Note:** The dry run uses the same GPG signing secrets as real publishing, but skips the Sonatype upload step.
52+
53+
#### Local Testing
54+
55+
You can also test the publishing process locally (signing is optional):
56+
57+
**Complete local testing script:**
58+
59+
```bash
60+
#!/bin/bash
61+
cd bindings/kotlin
62+
63+
# Test publish to Maven Local (no GPG required)
64+
./gradlew clean publishToMavenLocal --info
65+
66+
# Verify the results
67+
echo "Published artifacts:"
68+
find ~/.m2/repository/org/iota -name "*.jar" -o -name "*.pom" | head -5
69+
70+
echo "JAR contents check:"
71+
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
72+
echo "files found (should be > 1)"
73+
```

bindings/kotlin/build.gradle.kts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import com.ncorti.ktfmt.gradle.tasks.*
2+
import java.util.Base64
23

34
plugins {
45
kotlin("jvm") version "1.9.24"
56
kotlin("plugin.serialization") version "1.9.24"
67
id("com.ncorti.ktfmt.gradle") version "0.25.0"
78
application
9+
`maven-publish`
10+
signing
811
}
912

1013
group = "org.iota"
@@ -105,3 +108,68 @@ tasks.register("compileWithErrors") {
105108
}
106109
}
107110
}
111+
112+
publishing {
113+
publications {
114+
create<MavenPublication>("maven") {
115+
from(components["java"])
116+
pom {
117+
name.set("IOTA SDK Kotlin Bindings")
118+
description.set("Kotlin bindings for the IOTA SDK")
119+
url.set("https://github.com/iotaledger/iota-rust-sdk")
120+
licenses {
121+
license {
122+
name.set("Apache-2.0")
123+
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
124+
}
125+
}
126+
developers {
127+
developer {
128+
id.set("iotaledger")
129+
name.set("IOTA Foundation")
130+
email.set("contact@iota.org")
131+
}
132+
}
133+
scm {
134+
connection.set("scm:git:git://github.com/iotaledger/iota-rust-sdk.git")
135+
developerConnection.set("scm:git:ssh://github.com/iotaledger/iota-rust-sdk.git")
136+
url.set("https://github.com/iotaledger/iota-rust-sdk")
137+
}
138+
}
139+
}
140+
}
141+
repositories {
142+
maven {
143+
name = "ossrh"
144+
url =
145+
uri(
146+
if (version.toString().endsWith("SNAPSHOT")) {
147+
"https://s01.oss.sonatype.org/content/repositories/snapshots/"
148+
} else {
149+
"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
150+
}
151+
)
152+
val sonatypeUsername =
153+
providers.environmentVariable("ORG_GRADLE_PROJECT_SONATYPE_USERNAME")
154+
val sonatypePassword =
155+
providers.environmentVariable("ORG_GRADLE_PROJECT_SONATYPE_PASSWORD")
156+
if (sonatypeUsername.isPresent && sonatypePassword.isPresent) {
157+
credentials {
158+
username = sonatypeUsername.get()
159+
password = sonatypePassword.get()
160+
}
161+
}
162+
}
163+
}
164+
}
165+
166+
signing {
167+
val signingKeyEncoded =
168+
providers.environmentVariable("ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY")
169+
val signingPassword = providers.environmentVariable("ORG_GRADLE_PROJECT_SIGNING_PASSWORD")
170+
if (signingKeyEncoded.isPresent && signingPassword.isPresent) {
171+
val signingKey = String(Base64.getDecoder().decode(signingKeyEncoded.get()))
172+
useInMemoryPgpKeys(signingKey, signingPassword.get())
173+
sign(publishing.publications["maven"])
174+
}
175+
}

0 commit comments

Comments
 (0)