From bb73b64e296f72132702d4da1507783699ee3b7b Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 14 Jun 2024 09:46:12 +0100 Subject: [PATCH] Add example of using JNI to call into Rust (#2690) --- examples/.bazelrc | 5 ++ examples/WORKSPACE.bazel | 46 ++++++++++++++++- .../com/example/rustjni/BUILD.bazel | 21 ++++++++ .../com/example/rustjni/RustJniTest.java | 15 ++++++ .../com/example/rustjni/RustStringLength.java | 29 +++++++++++ .../java_calling_rust/rust-crate/BUILD.bazel | 8 +++ .../rust-crate/rust_string_length.rs | 16 ++++++ examples/maven_install.json | 49 +++++++++++++++++++ 8 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 examples/ffi/java_calling_rust/com/example/rustjni/BUILD.bazel create mode 100644 examples/ffi/java_calling_rust/com/example/rustjni/RustJniTest.java create mode 100644 examples/ffi/java_calling_rust/com/example/rustjni/RustStringLength.java create mode 100644 examples/ffi/java_calling_rust/rust-crate/BUILD.bazel create mode 100644 examples/ffi/java_calling_rust/rust-crate/rust_string_length.rs create mode 100644 examples/maven_install.json diff --git a/examples/.bazelrc b/examples/.bazelrc index 05952d1165..4bdd2dcae8 100644 --- a/examples/.bazelrc +++ b/examples/.bazelrc @@ -21,6 +21,11 @@ common --noenable_bzlmod # This isn't currently the defaut in Bazel, but we enable it to test we'll be ready if/when it flips. build --incompatible_disallow_empty_glob +build --java_runtime_version=remotejdk_21 +build --java_language_version=21 +build --tool_java_runtime_version=remotejdk_21 +build --tool_java_language_version=21 + # This import should always be last to allow users to override # settings for local development. try-import %workspace%/user.bazelrc diff --git a/examples/WORKSPACE.bazel b/examples/WORKSPACE.bazel index 7d892e1780..610b2047d4 100644 --- a/examples/WORKSPACE.bazel +++ b/examples/WORKSPACE.bazel @@ -34,6 +34,17 @@ load("@rules_rust//bindgen:transitive_repositories.bzl", "rust_bindgen_transitiv rust_bindgen_transitive_dependencies() +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# We need to load rules_java before anything proto-related happens because otherwise it will pull in its own rules_java which isn't compatible with rules_jvm_external. +http_archive( + name = "rules_java", + sha256 = "f8ae9ed3887df02f40de9f4f7ac3873e6dd7a471f9cddf63952538b94b59aeb3", + urls = [ + "https://github.com/bazelbuild/rules_java/releases/download/7.6.1/rules_java-7.6.1.tar.gz", + ], +) + load("@rules_rust//proto/protobuf:repositories.bzl", "rust_proto_protobuf_dependencies", "rust_proto_protobuf_register_toolchains") rust_proto_protobuf_dependencies() @@ -58,8 +69,6 @@ load("@rules_rust//wasm_bindgen/rules_js:repositories.bzl", "js_rust_wasm_bindge js_rust_wasm_bindgen_dependencies() -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - ############################################################################### # Workspace examples ############################################################################### @@ -161,6 +170,39 @@ http_archive( ], ) +http_archive( + name = "rules_jvm_external", + sha256 = "08ea921df02ffe9924123b0686dc04fd0ff875710bfadb7ad42badb931b0fd50", + strip_prefix = "rules_jvm_external-6.1", + url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/6.1/rules_jvm_external-6.1.tar.gz", +) + +load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") + +rules_jvm_external_deps() + +load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") + +rules_jvm_external_setup() + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + name = "maven", + artifacts = [ + "net.java.dev.jna:jna:5.14.0", + "org.hamcrest:hamcrest:2.2", + ], + maven_install_json = "@//:maven_install.json", + repositories = [ + "https://repo1.maven.org/maven2", + ], +) + +load("@maven//:defs.bzl", "pinned_maven_install") + +pinned_maven_install() + ############################################################################### http_archive( diff --git a/examples/ffi/java_calling_rust/com/example/rustjni/BUILD.bazel b/examples/ffi/java_calling_rust/com/example/rustjni/BUILD.bazel new file mode 100644 index 0000000000..6f2305eea3 --- /dev/null +++ b/examples/ffi/java_calling_rust/com/example/rustjni/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") + +java_library( + name = "rustjni", + srcs = ["RustStringLength.java"], + data = ["//ffi/java_calling_rust/rust-crate:rstrlen"], + deps = [ + "@bazel_tools//tools/java/runfiles", + "@maven//:net_java_dev_jna_jna", + ], +) + +java_test( + name = "rustjni_test", + srcs = ["RustJniTest.java"], + test_class = "com.example.rustjni.RustJniTest", + deps = [ + ":rustjni", + "@maven//:org_hamcrest_hamcrest", + ], +) diff --git a/examples/ffi/java_calling_rust/com/example/rustjni/RustJniTest.java b/examples/ffi/java_calling_rust/com/example/rustjni/RustJniTest.java new file mode 100644 index 0000000000..ba61b3c159 --- /dev/null +++ b/examples/ffi/java_calling_rust/com/example/rustjni/RustJniTest.java @@ -0,0 +1,15 @@ +package com.example.rustjni; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class RustJniTest { + @Test + public void testCallsJniToRust() throws Exception { + final String s = "hello"; + long result = RustStringLength.loadNativeLibrary().calculate_string_length_from_rust(s); + assertThat(result, equalTo(5L)); + } +} diff --git a/examples/ffi/java_calling_rust/com/example/rustjni/RustStringLength.java b/examples/ffi/java_calling_rust/com/example/rustjni/RustStringLength.java new file mode 100644 index 0000000000..fa2e74caaa --- /dev/null +++ b/examples/ffi/java_calling_rust/com/example/rustjni/RustStringLength.java @@ -0,0 +1,29 @@ +package com.example.rustjni; + +import com.google.devtools.build.runfiles.AutoBazelRepository; +import com.google.devtools.build.runfiles.Runfiles; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +import java.io.IOException; + +@AutoBazelRepository +public interface RustStringLength extends Library { + long calculate_string_length_from_rust(String s); + + static RustStringLength loadNativeLibrary() throws IOException { + String prefix = "lib"; + String extension = "so"; + if ("Mac OS X".equals(System.getProperty("os.name"))) { + extension = "dylib"; + } else if (System.getProperty("os.name").contains("Windows")) { + prefix = ""; + extension = "dll"; + } + Runfiles.Preloaded runfiles = Runfiles.preload(); + String dylibPath = runfiles.withSourceRepository(AutoBazelRepository_RustStringLength.NAME).rlocation("examples/ffi/java_calling_rust/rust-crate/" + prefix + "rstrlen." + extension); + + return Native.load(dylibPath, RustStringLength.class); + } +} diff --git a/examples/ffi/java_calling_rust/rust-crate/BUILD.bazel b/examples/ffi/java_calling_rust/rust-crate/BUILD.bazel new file mode 100644 index 0000000000..529c121baa --- /dev/null +++ b/examples/ffi/java_calling_rust/rust-crate/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_rust//rust:defs.bzl", "rust_shared_library") + +rust_shared_library( + name = "rstrlen", + srcs = ["rust_string_length.rs"], + edition = "2021", + visibility = ["//visibility:public"], +) diff --git a/examples/ffi/java_calling_rust/rust-crate/rust_string_length.rs b/examples/ffi/java_calling_rust/rust-crate/rust_string_length.rs new file mode 100644 index 0000000000..5b3c11f81f --- /dev/null +++ b/examples/ffi/java_calling_rust/rust-crate/rust_string_length.rs @@ -0,0 +1,16 @@ +use std::ffi::CStr; +use std::os::raw::c_char; + +/// Calculates the length of a string. +/// +/// # Safety +/// +/// The argument must null-terminated. +#[no_mangle] +pub unsafe extern "C" fn calculate_string_length_from_rust(s: *const c_char) -> i64 { + let slice = unsafe { CStr::from_ptr(s).to_bytes() }; + std::str::from_utf8(slice).map_or(-1, |s| { + let l = s.len(); + l.try_into().unwrap_or(-1) + }) +} diff --git a/examples/maven_install.json b/examples/maven_install.json new file mode 100644 index 0000000000..ca3192bb6d --- /dev/null +++ b/examples/maven_install.json @@ -0,0 +1,49 @@ +{ + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", + "__INPUT_ARTIFACTS_HASH": -152875169, + "__RESOLVED_ARTIFACTS_HASH": 61617868, + "artifacts": { + "net.java.dev.jna:jna": { + "shasums": { + "jar": "34ed1e1f27fa896bca50dbc4e99cf3732967cec387a7a0d5e3486c09673fe8c6" + }, + "version": "5.14.0" + }, + "org.hamcrest:hamcrest": { + "shasums": { + "jar": "5e62846a89f05cd78cd9c1a553f340d002458380c320455dd1f8fc5497a8a1c1" + }, + "version": "2.2" + } + }, + "dependencies": {}, + "packages": { + "net.java.dev.jna:jna": [ + "com.sun.jna", + "com.sun.jna.internal", + "com.sun.jna.ptr", + "com.sun.jna.win32" + ], + "org.hamcrest:hamcrest": [ + "org.hamcrest", + "org.hamcrest.beans", + "org.hamcrest.collection", + "org.hamcrest.comparator", + "org.hamcrest.core", + "org.hamcrest.internal", + "org.hamcrest.io", + "org.hamcrest.number", + "org.hamcrest.object", + "org.hamcrest.text", + "org.hamcrest.xml" + ] + }, + "repositories": { + "https://repo1.maven.org/maven2/": [ + "net.java.dev.jna:jna", + "org.hamcrest:hamcrest" + ] + }, + "services": {}, + "version": "2" +}