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
51 changes: 38 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,34 @@ jobs:
os: [ubuntu-latest, macos-latest]

steps:
- uses: actions/checkout@v4
- name: Check out zenoh-java
uses: actions/checkout@v4
with:
path: zenoh-java

- name: Check out zenoh-flat-jni
uses: actions/checkout@v4
with:
repository: ZettaScaleLabs/zenoh-flat-jni
ref: 9bc155b3272c8c905bf95614cb7eb7e6d656c72f
path: zenoh-flat-jni

- name: Check out zenoh-flat
uses: actions/checkout@v4
with:
repository: ZettaScaleLabs/zenoh-flat
ref: 6d22091d75912be1c14330b0f78fa1d4bf258ea3
path: zenoh-flat

- name: Use prebindgen from GitHub main
shell: bash
working-directory: zenoh-flat-jni
run: |
sed -i.bak \
-e 's|prebindgen = { path = "../prebindgen/prebindgen" }|prebindgen = { git = "https://github.com/milyin/prebindgen.git", branch = "main" }|g' \
-e 's|prebindgen-proc-macro = { path = "../prebindgen/prebindgen-proc-macro",|prebindgen-proc-macro = { git = "https://github.com/milyin/prebindgen.git", branch = "main",|g' \
Cargo.toml ../zenoh-flat/Cargo.toml
rm Cargo.toml.bak ../zenoh-flat/Cargo.toml.bak

- uses: actions/setup-java@v4
with:
Expand All @@ -32,31 +59,29 @@ jobs:
- name: Install Rust toolchain
run: |
rustup show
rustup component add rustfmt clippy
rustup component add rustfmt clippy --toolchain 1.93.0

- name: Cargo Format
working-directory: zenoh-jni
run: cargo fmt --all --check
working-directory: zenoh-flat-jni
run: cargo fmt --check

- name: Clippy Check without Cargo.lock
working-directory: zenoh-jni
run: |
rm Cargo.lock
cargo clippy --all-targets --all-features -- -D warnings
git restore Cargo.lock
- name: Clippy Check
working-directory: zenoh-flat-jni
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Check for feature leaks
working-directory: zenoh-jni
working-directory: zenoh-flat-jni
run: cargo test --no-default-features

- name: Build Zenoh-JNI
working-directory: zenoh-jni
- name: Build zenoh-flat-jni
working-directory: zenoh-flat-jni
run: cargo build

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Gradle Test
working-directory: zenoh-java
run: ./gradlew jvmTest --info

markdown_lint:
Expand Down
39 changes: 39 additions & 0 deletions ZENOH_FLAT_TRANSITION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# zenoh-flat transition

This branch (`zenoh-flat-transition`) is the **integration branch** for rebuilding
zenoh-java on top of the generated JNI/Kotlin bindings, replacing the hand-written
`zenoh-jni` layer. It exists so the transition can land as a series of reviewable
PRs targeting this branch instead of `main`; when the transition is complete, this
branch merges to `main` as a whole (and this file is removed).

## Architecture

```
zenoh (Rust)
└─ zenoh-flat flat #[prebindgen]-annotated Rust API
└─ zenoh-flat-jni generated JNI externs + Kotlin classes (prebindgen lang::JniGen)
└─ zenoh-java Kotlin SDK wrapper (this repo)
```

- **prebindgen** — <https://github.com/milyin/prebindgen> (generator)
- **zenoh-flat** — <https://github.com/ZettaScaleLabs/zenoh-flat> (flat Rust API)
- **zenoh-flat-jni** — <https://github.com/ZettaScaleLabs/zenoh-flat-jni> (generated bindings,
consumed as a sibling checkout in CI and via Gradle composite build locally;
as a Maven artifact once published)

## Constituent PRs

| PR | Scope | Status |
| --- | --- | --- |
| [#481](https://github.com/eclipse-zenoh/zenoh-java/pull/481) | Use receiver-style zenoh-flat-jni bindings (generated Session/Query/Publisher methods, split key-expr overloads, de-prefixed callback names); +61–83% subscriber throughput | merged |
| [#484](https://github.com/eclipse-zenoh/zenoh-java/pull/484) | `Encoding`/`ZenohId.toString` become pure JVM values (fixes a per-message native leak); the conversion logic lives in the **shared tier** (`EncodingCodec`/`zidString` in zenoh-flat-jni, correspondence-tested against native, reusable by zenoh-kotlin) with only a thin facade here; publisher **default encoding set natively at declare** (plain puts cross no encoding data). Pairs with [zenoh-flat-jni#4](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/4), [zenoh-flat#3](https://github.com/ZettaScaleLabs/zenoh-flat/pull/3) (merged), [prebindgen#80](https://github.com/milyin/prebindgen/pull/80) (merged) | in progress |

Companion PRs in the upstream repos are coordinated per constituent PR (e.g.
[ZettaScaleLabs/zenoh-flat-jni#3](https://github.com/ZettaScaleLabs/zenoh-flat-jni/pull/3)
pairs with #481 and merges first; CI here pins the exact upstream commits).

## CI pinning

`.github/workflows/ci.yml` on the constituent branches pins the exact
`zenoh-flat-jni` / `zenoh-flat` commits the code was written against, while
`prebindgen` resolves from its `main`. Pins are bumped as the upstream PRs land.
10 changes: 2 additions & 8 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,13 @@ tasks {

examples.forEach { example ->
register(example, JavaExec::class) {
dependsOn("CompileZenohJNI")
dependsOn(":zenoh-java:buildZenohFlatJni")
description = "Run the $example example"
mainClass.set("io.zenoh.$example")
classpath(sourceSets["main"].runtimeClasspath)
val zenohPaths = "../zenoh-jni/target/release"
val zenohPaths = "../zenoh-flat-jni/target/release"
val defaultJvmArgs = arrayListOf("-Djava.library.path=$zenohPaths")
jvmArgs(defaultJvmArgs)
}
}
}

tasks.register("CompileZenohJNI") {
project.exec {
commandLine("cargo", "build", "--release", "--manifest-path", "../zenoh-jni/Cargo.toml")
}
}
15 changes: 11 additions & 4 deletions examples/src/main/java/io/zenoh/ZSubThr.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.zenoh.exceptions.ZError;
import io.zenoh.keyexpr.KeyExpr;
import io.zenoh.pubsub.Subscriber;
import io.zenoh.sample.Sample;
import picocli.CommandLine;

import java.util.List;
Expand All @@ -40,7 +41,7 @@ public Integer call() throws Exception {
System.out.println("Opening Session");
try (Session session = Zenoh.open(config)) {
try (KeyExpr keyExpr = KeyExpr.tryFrom("test/thr")) {
subscriber = session.declareSubscriber(keyExpr, sample -> listener(number));
subscriber = session.declareSubscriber(keyExpr, sample -> listener(sample, number));
System.out.println("Press CTRL-C to quit...");

while (subscriber.isValid()) {
Expand All @@ -51,7 +52,12 @@ public Integer call() throws Exception {
return 0;
}

private void listener(long number) {
private void listener(Sample sample, long number) {
// Touch the delivered data so it actually reaches the Java surface
// (payload bytes + key-expression string): keeps lazy bindings honest
// and prevents either side from skipping materialization costs.
dataSink += sample.getPayload().toBytes().length
+ sample.getKeyExpr().toString().length();
if (batchCount > samples) {
closeSubscriber();
report();
Expand Down Expand Up @@ -86,8 +92,8 @@ private void report() {
double elapsedTimeSecs = (double) (end - globalStartTimestampNs) / NANOS_TO_SEC;
double averageMessagesPerSec = totalMessages / elapsedTimeSecs;

System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec%n",
totalMessages, elapsedTimeSecs, averageMessagesPerSec);
System.out.printf("Received %d messages in %.2f seconds: averaged %.2f msgs/sec (sink %d)%n",
totalMessages, elapsedTimeSecs, averageMessagesPerSec, dataSink);
}

private void closeSubscriber() {
Expand All @@ -114,6 +120,7 @@ private void closeSubscriber() {
private static final long NANOS_TO_SEC = 1_000_000_000L;
private long batchCount = 0;
private long count = 0;
private long dataSink = 0;
private long startTimestampNs = 0;
private long globalStartTimestampNs = 0;

Expand Down
6 changes: 5 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ rootProject.name = "zenoh-java"

include(":zenoh-java")
include(":examples")
include(":zenoh-jni")

// zenoh-flat-jni is now a separate repository (https://github.com/ZettaScaleLabs/zenoh-flat-jni)
// and is consumed as a Maven artifact: org.eclipse.zenoh:zenoh-flat-jni:VERSION
// For local development with coordinated changes, use Gradle composite build:
includeBuild("../zenoh-flat-jni")

plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
Expand Down
103 changes: 5 additions & 98 deletions zenoh-java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// ZettaScale Zenoh Team, <zenoh@zettascale.tech>
//

import com.nishtahir.CargoExtension

plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
Expand All @@ -30,26 +28,21 @@ val release = project.findProperty("release")?.toString()?.toBoolean() == true
// Modifying this property will affect the release workflows!
val isRemotePublication = project.findProperty("remotePublication")?.toString()?.toBoolean() == true

var buildMode = if (release) BuildMode.RELEASE else BuildMode.DEBUG
// zenoh-flat-jni is now consumed as a Maven artifact: org.eclipse.zenoh:zenoh-flat-jni:VERSION
// Native libraries are bundled in the JAR by the zenoh-flat-jni module

if (androidEnabled) {
apply(plugin = "com.android.library")
apply(plugin = "org.mozilla.rust-android-gradle.rust-android")

configureCargo()
configureAndroid()
}


kotlin {
jvmToolchain(11)
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
testRuns["test"].executionTask.configure {
val zenohPaths = "../zenoh-jni/target/$buildMode"
jvmArgs("-Djava.library.path=$zenohPaths")
}
if (!androidEnabled) {
withJava() // Adding java to a kotlin lib targeting android is incompatible
// The java code is only meant for testing and is non-critical for the android publication.
Expand All @@ -66,6 +59,8 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// Zenoh Flat JNI - includes Kotlin sources and native libraries
implementation("org.eclipse.zenoh:zenoh-flat-jni:1.9.0")
implementation("commons-net:commons-net:3.9.0")
implementation("com.google.guava:guava:33.3.1-jre")
}
Expand All @@ -82,18 +77,6 @@ kotlin {
}
}
}
val jvmMain by getting {
if (isRemotePublication) {
// The line below is intended to load the native libraries that are crosscompiled on GitHub actions when publishing a JVM package.
resources.srcDir("../jni-libs").include("*/**")
} else {
resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll"))
}
}

val jvmTest by getting {
resources.srcDir("../zenoh-jni/target/$buildMode").include(arrayListOf("*.dylib", "*.so", "*.dll"))
}
}

val javadocJar by tasks.registering(Jar::class) {
Expand Down Expand Up @@ -153,65 +136,6 @@ tasks.withType<PublishToMavenRepository>().configureEach {
dependsOn(tasks.withType<Sign>())
}

tasks.withType<Test> {
doFirst {
// The line below is added for the Android Unit tests which are equivalent to the JVM tests.
// For them to work we need to specify the path to the native library as a system property and not as a jvmArg.
systemProperty("java.library.path", "../zenoh-jni/target/$buildMode")
}
}

tasks.whenObjectAdded {
if ((this.name == "mergeDebugJniLibFolders" || this.name == "mergeReleaseJniLibFolders")) {
this.dependsOn("cargoBuild")
}
}

tasks.named("compileKotlinJvm") {
dependsOn("buildZenohJni")
}

tasks.register("buildZenohJni") {
doLast {
if (!isRemotePublication) {
// This is intended for local publications. For publications done through GitHub workflows,
// the zenoh-jni build is achieved and loaded differently from the CI
buildZenohJNI(buildMode)
}
}
}

fun buildZenohJNI(mode: BuildMode = BuildMode.DEBUG) {
val cargoCommand = mutableListOf("cargo", "build")

if (mode == BuildMode.RELEASE) {
cargoCommand.add("--release")
}

val result = project.exec {
commandLine(*(cargoCommand.toTypedArray()), "--manifest-path", "../zenoh-jni/Cargo.toml")
}

if (result.exitValue != 0) {
throw GradleException("Failed to build Zenoh-JNI.")
}

Thread.sleep(1000)
}

enum class BuildMode {
DEBUG {
override fun toString(): String {
return "debug"
}
},
RELEASE {
override fun toString(): String {
return "release"
}
}
}

fun Project.configureAndroid() {
extensions.configure<com.android.build.gradle.LibraryExtension>("android") {
namespace = "io.zenoh"
Expand Down Expand Up @@ -249,20 +173,3 @@ fun Project.configureAndroid() {
}
}
}

fun Project.configureCargo() {
extensions.configure<CargoExtension>("cargo") {
pythonCommand = "python3"
module = "../zenoh-jni"
libname = "zenoh-jni"
targetIncludes = arrayOf("libzenoh_jni.so")
targetDirectory = "../zenoh-jni/target/"
profile = "release"
targets = arrayListOf(
"arm",
"arm64",
"x86",
"x86_64",
)
}
}
27 changes: 0 additions & 27 deletions zenoh-java/src/androidMain/kotlin/io.zenoh/Zenoh.kt

This file was deleted.

Loading
Loading