Skip to content

Commit 95079b7

Browse files
authored
LibraryOverride fix and release current fixes (#59)
* Fix library override for absolutes, update changelog for this and the other unreleased change on main * Update for release
1 parent 4140852 commit 95079b7

5 files changed

Lines changed: 128 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.4.1
2+
3+
- fix resolving callback trait implementations declared in submodules of the current crate. Previously, traits like `my_crate::metrics::MetricsRecorder` failed with "no interface with module_path" during code generation.
4+
- fix Java keyword collisions in generated callback helper class names. Callback methods named after Java keywords (e.g., `record`) no longer produce invalid nested type declarations. Thanks @criccomini for this and the above fix!
5+
- fix `libraryOverride` system property to work with absolute paths. Previously, passing an absolute path via `-Duniffi.component.<namespace>.libraryOverride=/path/to/lib.so` would fail because it was always passed to `System.loadLibrary()`. Now absolute paths are correctly routed to `System.load()`.
6+
17
## 0.4.0
28

39
- switched from JNA generated bindings to FFM ones. Benchmarked performance speedup (via upstream benchmark suite) is from 4.2x-426x.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "uniffi-bindgen-java"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
authors = ["IronCore Labs <info@ironcorelabs.com>"]
55
readme = "README.md"
66
license = "MPL-2.0"

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ lower = "{}.toString()"
162162
rust-crate-name = "java.package.name"
163163
```
164164

165+
## Library Loading
166+
167+
By default, the generated code uses `System.loadLibrary()` to find the native library via `java.library.path`. If your native library lives outside the standard search paths or automatic discovery doesn't work for your environment, you can specify an absolute path at runtime using a system property:
168+
169+
```
170+
java -Duniffi.component.<namespace>.libraryOverride=/path/to/libmylib.so ...
171+
```
172+
173+
Where `<namespace>` is the UniFFI namespace of your component (e.g., `arithmetic`). When the override is an absolute path, the generated code uses `System.load()` instead of `System.loadLibrary()`, bypassing `java.library.path` entirely.
174+
175+
You can also pass a plain library name as the override, in which case it behaves like `System.loadLibrary()` and still requires the library to be on `java.library.path`.
176+
165177
## Notes
166178

167179
- failures in CompletableFutures will cause them to `completeExceptionally`. The error that caused the failure can be checked with `e.getCause()`. When implementing an async Rust trait in Java, you'll need to `completeExceptionally` instead of throwing. See `TestFixtureFutures.java` for an example trait implementation with errors.

src/templates/NamespaceLibraryTemplate.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ static synchronized String findLibraryName(String componentName) {
1010
}
1111

1212
static java.lang.foreign.SymbolLookup loadLibrary() {
13-
System.loadLibrary(findLibraryName("{{ ci.namespace() }}"));
13+
String name = findLibraryName("{{ ci.namespace() }}");
14+
if (name.startsWith("/") // Unix absolute path
15+
|| name.startsWith("\\\\") // Windows UNC path
16+
|| (name.length() > 2 && name.charAt(1) == ':')) // Windows drive path (e.g. C:\)
17+
{
18+
System.load(name);
19+
} else {
20+
System.loadLibrary(name);
21+
}
1422
return java.lang.foreign.SymbolLookup.loaderLookup();
1523
}
1624

tests/tests.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,97 @@ fn run_test(fixture_name: &str, test_file: &str) -> Result<()> {
143143
Ok(())
144144
}
145145

146+
/// Run a test using an absolute path library override instead of java.library.path.
147+
/// This validates that the generated loadLibrary() code uses System.load() for absolute paths.
148+
fn run_test_with_library_override(
149+
fixture_name: &str,
150+
test_file: &str,
151+
namespace: &str,
152+
) -> Result<()> {
153+
let test_path = Utf8Path::new(".").join("tests").join(test_file);
154+
let test_helper = UniFFITestHelper::new(fixture_name)?;
155+
// Use a synthetic path for out_dir so it doesn't collide with run_test's out_dir
156+
// (create_out_dir is deterministic based on the path).
157+
let out_dir_key = Utf8Path::new(".")
158+
.join("tests")
159+
.join("library_override")
160+
.join(test_file);
161+
let out_dir = test_helper.create_out_dir(env!("CARGO_TARGET_TMPDIR"), &out_dir_key)?;
162+
let cdylib_path = test_helper.cdylib_path()?;
163+
164+
let mut paths = BindgenPaths::default();
165+
paths.add_cargo_metadata_layer(false)?;
166+
let loader = BindgenLoader::new(paths);
167+
168+
generate(
169+
&loader,
170+
&GenerateOptions {
171+
source: cdylib_path.clone(),
172+
out_dir: out_dir.clone(),
173+
format: true,
174+
crate_filter: None,
175+
},
176+
)?;
177+
178+
// Copy the cdylib to a known absolute path (no symlink needed since we pass the full path)
179+
let native_lib_dir = out_dir.join("native");
180+
fs::create_dir_all(&native_lib_dir)?;
181+
let cdylib_filename = cdylib_path.file_name().unwrap();
182+
let extension = cdylib_path.extension().unwrap();
183+
let lib_base_name = cdylib_filename
184+
.strip_prefix("lib")
185+
.unwrap_or(cdylib_filename)
186+
.split('-')
187+
.next()
188+
.unwrap_or(cdylib_filename);
189+
let canonical_lib_name = format!("lib{}.{}", lib_base_name, extension);
190+
let lib_absolute_path = native_lib_dir.join(&canonical_lib_name);
191+
fs::copy(&cdylib_path, &lib_absolute_path)?;
192+
193+
let jar_file = build_jar(fixture_name, &out_dir)?;
194+
195+
let status = Command::new("javac")
196+
.arg("-classpath")
197+
.arg(calc_classpath(vec![&out_dir, &jar_file]))
198+
.arg("-Werror")
199+
.arg(&test_path)
200+
.spawn()
201+
.context("Failed to spawn `javac` to compile Java test")?
202+
.wait()
203+
.context("Failed to wait for `javac` when compiling Java test")?;
204+
if !status.success() {
205+
anyhow::bail!("running `javac` failed when compiling the Java test")
206+
}
207+
208+
// Run with library override set to an absolute path and NO java.library.path,
209+
// so this can only work if the generated code uses System.load() for absolute paths.
210+
let compiled_path = test_path.file_stem().unwrap();
211+
let run_status = Command::new("java")
212+
.arg("-ea")
213+
.arg("--enable-native-access=ALL-UNNAMED")
214+
.arg(format!(
215+
"-Duniffi.component.{}.libraryOverride={}",
216+
namespace, lib_absolute_path
217+
))
218+
// Deliberately NOT setting -Djava.library.path
219+
.arg("-classpath")
220+
.arg(calc_classpath(vec![
221+
&out_dir,
222+
&jar_file,
223+
&test_path.parent().unwrap().to_path_buf(),
224+
]))
225+
.arg(compiled_path)
226+
.spawn()
227+
.context("Failed to spawn `java` to run Java test")?
228+
.wait()
229+
.context("Failed to wait for `java` when running Java test")?;
230+
if !run_status.success() {
231+
anyhow::bail!("Running the `java` test with library override failed.")
232+
}
233+
234+
Ok(())
235+
}
236+
146237
/// Get the uniffi_toml of the fixture if it exists.
147238
/// It looks for it in the root directory of the project `name`.
148239
fn find_uniffi_toml(name: &str) -> Result<Option<Utf8PathBuf>> {
@@ -286,3 +377,12 @@ fixture_tests! {
286377
(test_rename, "uniffi-fixture-rename", "scripts/TestRename/TestRename.java"),
287378
(test_primitive_arrays, "uniffi-fixture-primitive-arrays", "scripts/TestPrimitiveArrays.java"),
288379
}
380+
381+
#[test]
382+
fn test_library_override_absolute_path() -> Result<()> {
383+
run_test_with_library_override(
384+
"uniffi-example-arithmetic",
385+
"scripts/TestArithmetic.java",
386+
"arithmetic",
387+
)
388+
}

0 commit comments

Comments
 (0)