app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 562472657ea5ab525d9771df5e3a9d86ccbc606a
parent e31489275629cb9f98e346df9a147144bdee90e5
Author: triesap <tyson@radroots.org>
Date:   Sun,  2 Aug 2026 19:47:04 +0000

build(desktop): stage the native runtime

- place the current-host library under JNA platform resources
- configure development and test native lookup paths
- verify generated Kotlin crosses the loaded native ABI
- include the library in the current-host application bundle

Diffstat:
Mapp/desktop/build.gradle.kts | 34++++++++++++++++++++++++++++++++++
Aapp/desktop/src/test/kotlin/org/radroots/studio/ffi/NativeLoaderTest.kt | 11+++++++++++
Mcore/crates/ffi/src/lib.rs | 14++++++++++++++
Mdocs/architecture/rust-ffi-boundary.md | 6++++++
Mdocs/implementation/nostr-runtime-rcld.md | 6+++---
5 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/app/desktop/build.gradle.kts b/app/desktop/build.gradle.kts @@ -32,6 +32,16 @@ val rustLibraryName = when { } val rustDebugLibrary = rootProject.layout.projectDirectory .file("core/target/debug/$rustLibraryName") +val jnaPlatformPrefix = when { + System.getProperty("os.name").startsWith("Mac") && + System.getProperty("os.arch") == "aarch64" -> "darwin-aarch64" + System.getProperty("os.name").startsWith("Mac") -> "darwin-x86-64" + System.getProperty("os.name").startsWith("Windows") && + System.getProperty("os.arch") == "aarch64" -> "win32-aarch64" + System.getProperty("os.name").startsWith("Windows") -> "win32-x86-64" + System.getProperty("os.arch") == "aarch64" -> "linux-aarch64" + else -> "linux-x86-64" +} val buildRustCore by tasks.registering(Exec::class) { workingDir(rootProject.projectDir) @@ -48,6 +58,7 @@ val buildRustCore by tasks.registering(Exec::class) { } val generatedUniFfiKotlin = layout.buildDirectory.dir("generated/uniffi/kotlin") +val generatedNativeResources = layout.buildDirectory.dir("generated/uniffi/native-resources") val generateUniFfiKotlin by tasks.registering(Exec::class) { dependsOn(buildRustCore) workingDir(rootProject.projectDir) @@ -77,6 +88,11 @@ val generateUniFfiKotlin by tasks.registering(Exec::class) { inputs.file(rootProject.layout.projectDirectory.file("core/crates/ffi/uniffi.toml")) outputs.dir(generatedUniFfiKotlin) } +val stageUniFfiNativeLibrary by tasks.registering(Copy::class) { + dependsOn(buildRustCore) + from(rustDebugLibrary) + into(generatedNativeResources.map { it.dir(jnaPlatformPrefix) }) +} dependencies { implementation(compose.desktop.currentOs) @@ -121,6 +137,7 @@ tasks.withType<Test>().configureEach { kotlin { sourceSets.main { kotlin.srcDir(generatedUniFfiKotlin) + resources.srcDir(generatedNativeResources) } compilerOptions { @@ -133,6 +150,23 @@ kotlin { tasks.named("compileKotlin") { dependsOn(generateUniFfiKotlin) } +tasks.named("processResources") { + dependsOn(stageUniFfiNativeLibrary) +} +tasks.withType<Test>().configureEach { + dependsOn(buildRustCore) + systemProperty( + "jna.library.path", + rustDebugLibrary.asFile.parentFile.absolutePath, + ) +} +tasks.withType<JavaExec>().configureEach { + dependsOn(buildRustCore) + systemProperty( + "jna.library.path", + rustDebugLibrary.asFile.parentFile.absolutePath, + ) +} compose.desktop { application { diff --git a/app/desktop/src/test/kotlin/org/radroots/studio/ffi/NativeLoaderTest.kt b/app/desktop/src/test/kotlin/org/radroots/studio/ffi/NativeLoaderTest.kt @@ -0,0 +1,11 @@ +package org.radroots.studio.ffi + +import kotlin.test.Test +import kotlin.test.assertEquals + +class NativeLoaderTest { + @Test + fun generatedBindingLoadsTheCurrentHostLibrary() { + assertEquals("0.1.0-alpha.0", nativeRuntimeVersion()) + } +} diff --git a/core/crates/ffi/src/lib.rs b/core/crates/ffi/src/lib.rs @@ -12,3 +12,17 @@ pub use dto::{ pub use observer::{ObserverSubscription, StudioObserver}; uniffi::setup_scaffolding!(); + +#[uniffi::export] +#[must_use] +pub fn native_runtime_version() -> String { + env!("CARGO_PKG_VERSION").to_owned() +} + +#[cfg(test)] +mod tests { + #[test] + fn native_runtime_reports_the_crate_version() { + assert_eq!(super::native_runtime_version(), "0.1.0-alpha.0"); + } +} diff --git a/docs/architecture/rust-ffi-boundary.md b/docs/architecture/rust-ffi-boundary.md @@ -50,3 +50,9 @@ generator against that dynamic library and writes Kotlin to `app/desktop/build/generated/uniffi/kotlin`. The main Kotlin source set reads that generated directory, and `compileKotlin` depends on generation. Generated bindings are ignored build output and are never committed. + +Development and tests point JNA at `core/target/debug`. Packaging stages the +current-host library under JNA's platform resource prefix inside the desktop +resources, allowing the packaged JVM runtime to extract and load it without a +machine-specific absolute path. `NativeLoaderTest` crosses the generated ABI +and verifies the native crate version without opening storage or credentials. diff --git a/docs/implementation/nostr-runtime-rcld.md b/docs/implementation/nostr-runtime-rcld.md @@ -378,7 +378,7 @@ async stale-result/cancellation tests, profile cache tests, and workspace tests. ### RCLD-09: UniFFI and native build integration -Status: pending. +Status: completed. Scope: checkpoints 41 through 47. Add UniFFI definitions and safe DTO mapping, expose supported AppCore commands, implement observer handles, build the native @@ -527,7 +527,7 @@ handoff commit sequence. - [x] 44. Build native library artifacts from Cargo. - [x] 45. Add Gradle task for Cargo build. - [x] 46. Add Gradle UniFFI binding generation and generated source set. -- [ ] 47. Stage native library for development, tests, and packaged app. +- [x] 47. Stage native library for development, tests, and packaged app. ### RCLD-10 @@ -565,7 +565,7 @@ handoff commit sequence. - [x] RCLD-06: Account generation and import. - [x] RCLD-07: Account lifecycle and recovery. - [x] RCLD-08: Relay and profile runtime. -- [ ] RCLD-09: UniFFI and native build integration. +- [x] RCLD-09: UniFFI and native build integration. - [ ] RCLD-10: Thin Kotlin shell and minimal UI. - [ ] RCLD-11: End-to-end integration hardening. - [ ] RCLD-12: Documentation and acceptance reconciliation.