diff --git a/.github/workflows/maven-central-publish.yml b/.github/workflows/maven-central-publish.yml new file mode 100644 index 000000000..400a74e27 --- /dev/null +++ b/.github/workflows/maven-central-publish.yml @@ -0,0 +1,137 @@ +name: Publish to Maven Central + +on: + 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: 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_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') + run: | + cd bindings/kotlin + ./gradlew publishAndReleaseToMavenCentral --no-daemon --no-parallel --no-configuration-cache + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }} + + - name: Dry run - Local publish only + if: github.event_name == 'push' || (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 --no-configuration-cache + env: + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ORG_GRADLE_PROJECT_BASE64_ENCODED_ASCII_ARMORED_SIGNING_KEY }} diff --git a/Makefile b/Makefile index ccaa3c5e1..5cb95f9ec 100644 --- a/Makefile +++ b/Makefile @@ -105,9 +105,9 @@ bindings-examples-format: ## Format all bindings examples 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 @@ -116,21 +116,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 @@ -176,13 +182,13 @@ kotlin-examples: ## Run all Kotlin bindings examples .PHONY: kotlin-examples-format-check kotlin-examples-format-check: ## Check format of all Kotlin bindings examples cd bindings/kotlin; \ - ./gradlew KtfmtCheck || exit $$?; \ + ./gradlew KtfmtCheck --no-configuration-cache || exit $$?; \ cd - .PHONY: kotlin-examples-format kotlin-examples-format: ## Format all Kotlin bindings examples cd bindings/kotlin; \ - ./gradlew KtfmtFormat; \ + ./gradlew KtfmtFormat --no-configuration-cache; \ cd - .PHONY: python-example diff --git a/bindings/kotlin/README.md b/bindings/kotlin/README.md index 1e056f69f..e1c5c6da3 100644 --- a/bindings/kotlin/README.md +++ b/bindings/kotlin/README.md @@ -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)" +``` diff --git a/bindings/kotlin/build.gradle.kts b/bindings/kotlin/build.gradle.kts index 8be08cc7b..4359a82de 100644 --- a/bindings/kotlin/build.gradle.kts +++ b/bindings/kotlin/build.gradle.kts @@ -1,15 +1,18 @@ import com.ncorti.ktfmt.gradle.tasks.* +import java.util.Base64 plugins { kotlin("jvm") version "1.9.24" kotlin("plugin.serialization") version "1.9.24" id("com.ncorti.ktfmt.gradle") version "0.25.0" + id("com.vanniktech.maven.publish") version "0.30.0" application + signing } group = "org.iota" -version = "1.0-SNAPSHOT" +version = "0.0.1-alpha.1" repositories { mavenCentral() } @@ -85,8 +88,6 @@ tasks.withType { "-Xno-receiver-assertions", // Add these flags to help with recursive type issues "-Xtype-enhancement-improvements-strict-mode=false", - "-Xskip-runtime-version-check", - "-Xlenient-function-type-parameter-checks", ) allWarningsAsErrors = false suppressWarnings = true @@ -105,3 +106,44 @@ tasks.register("compileWithErrors") { } } } + +mavenPublishing { + publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL) + signAllPublications() + + coordinates("org.iota", "iota-sdk", version.toString()) + + 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("contact@iota.org") + } + } + 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") + } + } +} + +signing { + val signingKeyEncoded = providers.environmentVariable("ORG_GRADLE_PROJECT_signingInMemoryKey") + val signingPassword = + providers.environmentVariable("ORG_GRADLE_PROJECT_signingInMemoryKeyPassword") + if (signingKeyEncoded.isPresent && signingPassword.isPresent) { + val signingKey = String(Base64.getDecoder().decode(signingKeyEncoded.get())) + useInMemoryPgpKeys(signingKey, signingPassword.get()) + } +} diff --git a/bindings/kotlin/gradle/wrapper/gradle-wrapper.properties b/bindings/kotlin/gradle/wrapper/gradle-wrapper.properties index ff23a68d7..bad7c2462 100644 --- a/bindings/kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/bindings/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/bindings/kotlin/gradlew b/bindings/kotlin/gradlew index 23d15a936..adff685a0 100755 --- a/bindings/kotlin/gradlew +++ b/bindings/kotlin/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/bindings/kotlin/gradlew.bat b/bindings/kotlin/gradlew.bat index db3a6ac20..c4bdd3ab8 100644 --- a/bindings/kotlin/gradlew.bat +++ b/bindings/kotlin/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/bindings/kotlin/settings.gradle.kts b/bindings/kotlin/settings.gradle.kts index d6ea9d2f3..a77eeed9d 100644 --- a/bindings/kotlin/settings.gradle.kts +++ b/bindings/kotlin/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "iota-sdk-jvm" +rootProject.name = "iota-sdk" include(":lib")