Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Force-link netstream's rb_net_* exports via #[used] keepalives

dlopen kept failing with "cannot locate symbol rb_net_open". Root cause:
rustc's #[used] attribute doesn't propagate across rlib boundaries —
rockbox-server has its own _netstream keepalive mod, but when our cdylib
pulls rockbox-server in as an rlib, rustc dead-code-strips the entire
crate (including its keepalive trick) because nothing in our crates/expo
code directly references rockbox-server symbols.

Fix:
- Reference start_server / start_servers directly in daemon.rs (extern fn
+ #[used] static fn pointer) so rockbox-server stays alive.
- Reference rbnetstream::rb_net_{open,read,len,lseek,close} directly the
same way so the netstream rlib stays alive.
- Add `rbnetstream` (package name "netstream") as a direct optional dep
in crates/expo/Cargo.toml gated on embedded-daemon.

Result: rb_net_open / rb_net_read / etc. are now `T` (defined text) in
librockbox_expo.so instead of `U` (undefined dynamic ref).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

+41
+1
Cargo.lock
··· 9245 9245 dependencies = [ 9246 9246 "futures-util", 9247 9247 "jni", 9248 + "netstream", 9248 9249 "once_cell", 9249 9250 "prost", 9250 9251 "reqwest",
+4
crates/expo/Cargo.toml
··· 23 23 "dep:rockbox-settings", 24 24 "dep:rockbox-library", 25 25 "dep:rockbox-sys", 26 + "dep:rbnetstream", # for the #[used] keepalives in daemon.rs 26 27 "rockbox-server/fts5", # cascades to rockbox-graphql/fts5 + rockbox-rpc/fts5 27 28 ] 28 29 ··· 46 47 rockbox-settings = { path = "../settings", optional = true, default-features = false } 47 48 rockbox-library = { path = "../library", optional = true, default-features = false } 48 49 rockbox-sys = { path = "../sys", optional = true, default-features = false } 50 + # netstream's lib is named "rbnetstream"; we depend on it directly so the 51 + # #[used] keepalives in daemon.rs can take addresses of its C ABI exports. 52 + rbnetstream = { path = "../netstream", package = "netstream", optional = true } 49 53 50 54 # ── Android-only ─────────────────────────────────────────────────────────── 51 55 [target.'cfg(target_os = "android")'.dependencies]
+36
crates/expo/src/daemon.rs
··· 32 32 // the desktop Zig wrapper at zig/src/main.zig uses. 33 33 extern "C" { 34 34 fn main_c() -> c_int; 35 + // start_server / start_servers are extern "C" fns in crates/server. 36 + // We don't call them from Rust (the C firmware does, from 37 + // apps/server_thread.c::server_init), but the references below force 38 + // rustc to keep the rockbox-server rlib in the cdylib link — without 39 + // them rustc dead-code-strips the entire crate, taking with it the 40 + // _netstream keepalive trick that exports rb_net_open / rb_net_read / 41 + // etc. from rbnetstream. The C side then can't find those symbols 42 + // and dlopen fails at runtime. 43 + fn start_server(); 44 + fn start_servers(); 35 45 } 46 + 47 + /// `#[used]` keepalive: takes the address of start_server / start_servers 48 + /// so rustc treats them as live and pulls in their containing rlib. 49 + #[used] 50 + static _KEEPALIVE_START_SERVER: unsafe extern "C" fn() = start_server; 51 + #[used] 52 + static _KEEPALIVE_START_SERVERS: unsafe extern "C" fn() = start_servers; 53 + 54 + /// Same keepalive trick for the netstream Rust crate's C-ABI exports — 55 + /// the C firmware's streamfd.c calls rb_net_open / rb_net_read / etc., but 56 + /// rustc dead-code-strips them from the cdylib link unless we reference 57 + /// them from our own (the cdylib's) Rust code. rockbox-server already 58 + /// has its own keepalive mod, but it's in an rlib and `#[used]` doesn't 59 + /// propagate across rlib boundaries. 60 + #[used] 61 + static _KEEPALIVE_RB_NET_OPEN: unsafe extern "C" fn(*const c_char) -> i32 = 62 + rbnetstream::rb_net_open; 63 + #[used] 64 + static _KEEPALIVE_RB_NET_READ: unsafe extern "C" fn(i32, *mut std::ffi::c_void, usize) -> i64 = 65 + rbnetstream::rb_net_read; 66 + #[used] 67 + static _KEEPALIVE_RB_NET_LEN: extern "C" fn(i32) -> i64 = rbnetstream::rb_net_len; 68 + #[used] 69 + static _KEEPALIVE_RB_NET_LSEEK: extern "C" fn(i32, i64, i32) -> i64 = rbnetstream::rb_net_lseek; 70 + #[used] 71 + static _KEEPALIVE_RB_NET_CLOSE: extern "C" fn(i32) = rbnetstream::rb_net_close; 36 72 37 73 unsafe fn cstr_to_str<'a>(p: *const c_char) -> Option<&'a str> { 38 74 if p.is_null() {