Skip to content

Commit f7513d7

Browse files
authored
Replace JavaCPP with hand-written JNI (#102)
1 parent 1a00e2b commit f7513d7

171 files changed

Lines changed: 3517 additions & 4062 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-natives.yml

Lines changed: 322 additions & 46 deletions
Large diffs are not rendered by default.

.github/workflows/gradle-test.yml

Lines changed: 125 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,47 @@ on:
1010
# - cron: '0 */8 * * *'
1111

1212
jobs:
13+
# Major ROS 2 distros + Foxy for Ubuntu 20.04 / GLIBC 2.31 coverage.
14+
# Platform mapping: foxy=20.04, humble=22.04, jazzy/kilted=24.04, lyrical=26.04.
1315
test-linux:
14-
name: Linux (ROS 2 ${{ matrix.ros_distro }})
16+
name: Linux (ROS 2 ${{ matrix.ros_distro }} / Ubuntu ${{ matrix.ubuntu }})
1517
runs-on: ubuntu-latest
1618
timeout-minutes: 15
1719
strategy:
20+
fail-fast: false
1821
matrix:
19-
ros_distro: [humble, jazzy, kilted]
22+
include:
23+
- ros_distro: foxy
24+
ubuntu: '20.04'
25+
- ros_distro: humble
26+
ubuntu: '22.04'
27+
- ros_distro: jazzy
28+
ubuntu: '24.04'
29+
- ros_distro: kilted
30+
ubuntu: '24.04'
31+
- ros_distro: lyrical
32+
ubuntu: '26.04'
2033
container:
2134
image: osrf/ros:${{ matrix.ros_distro }}-desktop
35+
defaults:
36+
run:
37+
# Older ROS images (e.g. foxy/focal) default to dash in job containers.
38+
shell: bash
2239
steps:
2340
- name: Checkout
24-
uses: actions/checkout@v4
41+
uses: actions/checkout@v5
42+
43+
- name: Confirm ROS / Ubuntu environment
44+
run: |
45+
set -euo pipefail
46+
. /etc/os-release
47+
echo "ROS_DISTRO=${{ matrix.ros_distro }}"
48+
echo "Container Ubuntu: $PRETTY_NAME"
49+
test "$VERSION_ID" = "${{ matrix.ubuntu }}"
50+
test -f "/opt/ros/${{ matrix.ros_distro }}/setup.bash"
2551
2652
- name: Setup JDK 17
27-
uses: actions/setup-java@v4
53+
uses: actions/setup-java@v5
2854
with:
2955
java-version: '17'
3056
distribution: 'temurin'
@@ -36,22 +62,106 @@ jobs:
3662
if: failure()
3763
run: find . -name 'hs_err*' -type f -exec cat {} +
3864

65+
# Build the Android AAR and verify that bundled natives load on an x86_64 emulator.
66+
test-android:
67+
name: Android (AAR + native load)
68+
runs-on: ubuntu-latest
69+
timeout-minutes: 30
70+
steps:
71+
- name: Checkout
72+
uses: actions/checkout@v5
73+
74+
- name: Enable KVM
75+
run: |
76+
set -euo pipefail
77+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
78+
sudo udevadm control --reload-rules
79+
sudo udevadm trigger --name-match=kvm
80+
81+
- name: Setup JDK 17
82+
uses: actions/setup-java@v5
83+
with:
84+
java-version: '17'
85+
distribution: 'temurin'
86+
87+
- name: Setup Android SDK
88+
uses: android-actions/setup-android@v4
89+
90+
- name: Install Android SDK packages
91+
run: |
92+
set -euo pipefail
93+
# JNI libs are prebuilt in jniLibs; NDK is not required to assemble the AAR.
94+
sdkmanager --install \
95+
"platforms;android-35" \
96+
"build-tools;35.0.0" \
97+
"platform-tools" \
98+
"emulator" \
99+
"system-images;android-30;google_apis;x86_64"
100+
101+
- name: Verify committed Android JNI libraries
102+
run: |
103+
set -euo pipefail
104+
for abi in arm64-v8a x86_64; do
105+
for lib in libfastcdr.so libfastdds.so libjnifastddsjava.so libc++_shared.so; do
106+
test -f "android/src/main/jniLibs/$abi/$lib" || {
107+
echo "Missing android/src/main/jniLibs/$abi/$lib"
108+
exit 1
109+
}
110+
done
111+
echo "OK jniLibs/$abi"
112+
ls -la "android/src/main/jniLibs/$abi"
113+
done
114+
115+
- name: Build Android release AAR
116+
working-directory: android
117+
run: ./gradlew assembleRelease --stacktrace
118+
119+
- name: Verify AAR packages JNI libraries
120+
working-directory: android
121+
run: |
122+
set -euo pipefail
123+
AAR=$(find build/outputs/aar -name '*.aar' | head -1)
124+
test -n "$AAR" || { echo "No AAR produced under build/outputs/aar"; find build -type f | head -50; exit 1; }
125+
echo "Built AAR: $AAR"
126+
unzip -l "$AAR"
127+
128+
for abi in arm64-v8a x86_64; do
129+
for lib in libfastcdr.so libfastdds.so libjnifastddsjava.so libc++_shared.so; do
130+
unzip -l "$AAR" | grep -q "jni/$abi/$lib" || {
131+
echo "AAR missing jni/$abi/$lib"
132+
exit 1
133+
}
134+
done
135+
echo "OK AAR contains jni/$abi natives"
136+
done
137+
138+
- name: Run Android native library load test
139+
uses: reactivecircus/android-emulator-runner@v2
140+
with:
141+
api-level: 30
142+
arch: x86_64
143+
target: google_apis
144+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
145+
disable-animations: true
146+
working-directory: android
147+
script: ./gradlew connectedDebugAndroidTest --stacktrace
148+
39149
test-docker:
40150
name: Docker Integration Tests
41151
runs-on: ubuntu-latest
42152
timeout-minutes: 15
43153
steps:
44154
- name: Checkout
45-
uses: actions/checkout@v4
155+
uses: actions/checkout@v5
46156

47157
- name: Setup JDK 17
48-
uses: actions/setup-java@v4
158+
uses: actions/setup-java@v5
49159
with:
50160
java-version: '17'
51161
distribution: 'temurin'
52162

53163
- name: Setup Docker
54-
uses: docker/setup-buildx-action@v3
164+
uses: docker/setup-buildx-action@v4
55165

56166
- name: Build JAR
57167
run: ./gradlew jar
@@ -63,12 +173,14 @@ jobs:
63173
name: Windows
64174
runs-on: windows-latest
65175
timeout-minutes: 10
176+
env:
177+
JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
66178
steps:
67179
- name: Checkout
68-
uses: actions/checkout@v4
180+
uses: actions/checkout@v5
69181

70182
- name: Setup JDK 17
71-
uses: actions/setup-java@v4
183+
uses: actions/setup-java@v5
72184
with:
73185
java-version: '17'
74186
distribution: 'temurin'
@@ -83,10 +195,10 @@ jobs:
83195
timeout-minutes: 10
84196
steps:
85197
- name: Checkout
86-
uses: actions/checkout@v4
198+
uses: actions/checkout@v5
87199

88200
- name: Setup JDK 17
89-
uses: actions/setup-java@v4
201+
uses: actions/setup-java@v5
90202
with:
91203
java-version: '17'
92204
distribution: 'temurin'
@@ -100,10 +212,10 @@ jobs:
100212
timeout-minutes: 10
101213
steps:
102214
- name: Checkout
103-
uses: actions/checkout@v4
215+
uses: actions/checkout@v5
104216

105217
- name: Setup JDK 17
106-
uses: actions/setup-java@v4
218+
uses: actions/setup-java@v5
107219
with:
108220
java-version: '17'
109221
distribution: 'temurin'

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
A ROS 2 library for Java. Uses Fast-DDS middleware. Fully compatible with other supported ROS 2 DDS middlewares.
1313

14-
Fast-DDS version: `3.2.2`
14+
Fast-DDS version: `3.6.2`
1515

16-
ROS 2 compatible and tested distros: `[humble, jazzy, kilted]`
16+
ROS 2 compatible and tested distros: `[foxy, humble, jazzy, kilted, lyrical]`
17+
(Foxy covers Ubuntu 20.04; Humble 22.04; Jazzy/Kilted 24.04; Lyrical 26.04.
18+
On Foxy, ros2 to jros2 interop is tested; jros2 to `ros2 topic echo` is covered on Humble+.)
1719

1820
Supported platforms:
1921
- Linux (Ubuntu 20.04+ or similar x86_64, arm64, armhf)
@@ -24,7 +26,7 @@ Supported platforms:
2426
Works on NVIDIA Jetson and all versions of Raspberry Pi!
2527

2628
## Features
27-
- Fully compatible with ROS 2 humble or newer (may also work with older ROS 2 distros)
29+
- Fully compatible with ROS 2 foxy or newer
2830
- Does not require a ROS 2 installation on the system
2931
- Ready-to-use Java library, just add to your Maven or Gradle dependencies!
3032
- Publish and subscribe to ROS 2 topics

android/build.gradle.kts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ android {
6565
minSdk = 26
6666

6767
consumerProguardFiles("consumer-rules.pro")
68+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
6869
}
6970

7071
buildTypes {
@@ -94,17 +95,16 @@ android {
9495
pickFirsts.add("**/libjnifastddsjava.so")
9596
}
9697
}
98+
99+
testOptions {
100+
animationsDisabled = true
101+
}
97102
}
98103

99104
dependencies {
100-
api("org.bytedeco:javacpp:1.5.11")
101-
api("us.ihmc:ihmc-native-library-loader:2.0.6")
102-
// Match Jackson version with ihmc-robot-data-logger
103-
api("com.fasterxml.jackson.core:jackson-databind:2.18.1")
104-
api("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1")
105-
// StAX API and implementation for Android (javax.xml.stream package)
106-
api("javax.xml.stream:stax-api:1.0-2")
107-
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
105+
androidTestImplementation("junit:junit:4.13.2")
106+
androidTestImplementation("androidx.test:runner:1.6.2")
107+
androidTestImplementation("androidx.test.ext:junit:1.2.1")
108108
}
109109

110110
tasks.register("copyLibcppShared") {
@@ -124,11 +124,16 @@ tasks.register("copyLibcppShared") {
124124
if (libcppPath.exists()) {
125125
val abiDir = file("$jniLibsDir/$abi")
126126
abiDir.mkdirs()
127-
copy {
128-
from(libcppPath)
129-
into(abiDir)
127+
val dest = file("$abiDir/libc++_shared.so")
128+
if (!dest.exists()) {
129+
copy {
130+
from(libcppPath)
131+
into(abiDir)
132+
}
133+
println("Copied libc++_shared.so for $abi from $libcppPath")
134+
} else {
135+
println("Keeping existing libc++_shared.so for $abi")
130136
}
131-
println("Copied libc++_shared.so for $abi from $libcppPath")
132137
} else {
133138
println("libc++_shared.so not found at $libcppPath")
134139
}
@@ -183,4 +188,4 @@ afterEvaluate {
183188
tasks.named("publishReleasePublicationToMavenLocal") {
184189
dependsOn("bundleReleaseAar")
185190
}
186-
}
191+
}

android/consumer-rules.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Consumer ProGuard rules for jros2-android (intentionally empty).

android/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
android.useAndroidX=true
2+
org.gradle.jvmargs=-Xmx2g

android/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pluginManagement {
55
repositories {
66
google()
77
mavenCentral()
8+
gradlePluginPortal()
89
}
910
}
1011

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
<uses-permission android:name="android.permission.INTERNET" />
4+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
5+
</manifest>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2025 Florida Institute for Human and Machine Cognition (IHMC)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package us.ihmc.jros2;
17+
18+
import androidx.test.ext.junit.runners.AndroidJUnit4;
19+
20+
import org.junit.Test;
21+
import org.junit.runner.RunWith;
22+
23+
import us.ihmc.fastddsjava.natives.fastddsjava;
24+
25+
import static org.junit.Assert.assertEquals;
26+
import static org.junit.Assert.assertNotEquals;
27+
28+
/**
29+
* Verifies that the AAR's bundled Fast-DDS / JNI libraries load and JNI resolves on device.
30+
*/
31+
@RunWith(AndroidJUnit4.class)
32+
public class NativeLibraryLoadTest
33+
{
34+
@Test
35+
public void nativeLibrariesLoadAndJniResolves()
36+
{
37+
// Load in dependency order (matches fastddsjavaNativeLibrary Android path).
38+
System.loadLibrary("log");
39+
System.loadLibrary("c++_shared");
40+
System.loadLibrary("fastcdr");
41+
System.loadLibrary("fastdds");
42+
System.loadLibrary("jnifastddsjava");
43+
44+
long type = fastddsjava.createTopicDataWrapperType("android_native_load_test", fastddsjava.CDR_LE);
45+
assertNotEquals(0L, type);
46+
assertEquals("android_native_load_test", fastddsjava.topicDataWrapperTypeGetName(type));
47+
}
48+
}
-294 KB
Binary file not shown.

0 commit comments

Comments
 (0)