Skip to content

Commit 298450f

Browse files
authored
fix: mobile linking (#56)
* fix: linking on android and ios
1 parent 2406430 commit 298450f

File tree

7 files changed

+112
-37
lines changed

7 files changed

+112
-37
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ vits-*
2424
sherpa-onnx-kws-*
2525
.tmp/
2626
jniLibs/
27+
build/

BUILDING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
192192

193193
### Debug build
194194

195-
For debug the build process of sherpa-onnx, please set `BUILD_DEBUG=1` environment variable before build.
195+
For debug the build process of sherpa-onnx, please set `SHERPA_BUILD_DEBUG=1` environment variable before build.
196196

197197
## Release new version
198198

@@ -220,7 +220,7 @@ You must install NDK from Android Studio settings.
220220
rustup target add aarch64-linux-android
221221
cargo install cargo-ndk
222222
export NDK_HOME="$HOME/Library/Android/sdk/ndk/27.0.12077973"
223-
cargo ndk -t arm64-v8a -o ./jniLibs build --release
223+
cargo ndk -t arm64-v8a build --release
224224
```
225225

226226
## Build for IOS
@@ -245,5 +245,6 @@ rustup target add aarch64-apple-ios-sim
245245
Build
246246

247247
```console
248+
export RUSTFLAGS="-C link-arg=-fapple-link-rtlib" # See https://github.com/bmrlab/gendam/issues/96
248249
cargo build --release --target aarch64-apple-ios
249250
```

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Rust bindings to [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx)
2222
- Windows
2323
- Linux
2424
- macOS
25+
- Android
26+
- IOS
2527

2628
## Install
2729

crates/sherpa-rs-sys/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ include = [
3737
bindgen = "0.69.4"
3838
cmake = "0.1"
3939
glob = "0.3.1"
40+
lazy_static = { version = "1.5.0" }
41+
4042
# Download binaries
4143
ureq = { version = "2.1", optional = true, default-features = false, features = [
4244
"tls",
@@ -49,7 +51,6 @@ sha2 = { version = "0.10", optional = true }
4951
dirs = { version = "5.0.1", optional = true }
5052
serde_json = { version = "1.0.134", optional = true }
5153
serde = { version = "1.0.216", features = ["derive"], optional = true }
52-
lazy_static = { version = "1.5.0", optional = true }
5354

5455

5556
[features]
@@ -63,7 +64,6 @@ download-binaries = [
6364
"dep:dirs",
6465
"dep:serde_json",
6566
"dep:serde",
66-
"dep:lazy_static",
6767
]
6868
static = []
6969
tts = []

crates/sherpa-rs-sys/build.rs

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,28 @@ lazy_static::lazy_static! {
2525
static ref RUST_CLANG_TARGET_MAP: HashMap<String, String> = {
2626
let mut m = HashMap::new();
2727
m.insert("aarch64-linux-android".to_string(), "armv8-linux-androideabi".to_string());
28+
m.insert("aarch64-apple-ios-sim".to_string(), "arm64-apple-ios-sim".to_string());
2829
m
2930
};
3031
}
3132

32-
fn hard_link(src: PathBuf, dst: PathBuf) {
33+
fn link_lib(lib: &str, is_dynamic: bool) {
34+
let lib_kind = if is_dynamic { "dylib" } else { "static" };
35+
debug_log!("cargo:rustc-link-lib={}={}", lib_kind, lib);
36+
println!("cargo:rustc-link-lib={}={}", lib_kind, lib);
37+
}
38+
39+
fn link_framework(framework: &str) {
40+
debug_log!("cargo:rustc-link-lib=framework={}", framework);
41+
println!("cargo:rustc-link-lib=framework={}", framework);
42+
}
43+
44+
fn add_search_path<P: AsRef<Path>>(path: P) {
45+
debug_log!("cargo:rustc-link-search={}", path.as_ref().display());
46+
println!("cargo:rustc-link-search={}", path.as_ref().display());
47+
}
48+
49+
fn copy_file(src: PathBuf, dst: PathBuf) {
3350
if let Err(err) = std::fs::hard_link(&src, &dst) {
3451
debug_log!("Failed to hardlink {:?}. fallback to copy.", err);
3552
fs::copy(&src, &dst)
@@ -215,6 +232,7 @@ fn main() {
215232
]);
216233

217234
let target = env::var("TARGET").unwrap();
235+
let is_mobile = target.contains("android") || target.contains("ios");
218236
debug_log!("TARGET: {:?}", target);
219237
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
220238

@@ -281,12 +299,15 @@ fn main() {
281299
bindings_builder = bindings_builder.clang_arg(format!("--target={}", clang_target));
282300
}
283301

302+
debug_log!("Generating bindings...");
284303
let bindings_builder = bindings_builder
285304
.generate()
286305
.expect("Failed to generate bindings");
287306

288307
// Write the generated bindings to an output file
289308
let bindings_path = out_dir.join("bindings.rs");
309+
310+
debug_log!("Writing bindings to {:?}", bindings_path);
290311
bindings_builder
291312
.write_to_file(bindings_path)
292313
.expect("Failed to write bindings");
@@ -339,7 +360,6 @@ fn main() {
339360
.always_configure(false);
340361

341362
let mut sherpa_libs: Vec<String> = Vec::new();
342-
let sherpa_libs_kind = if is_dynamic { "dylib" } else { "static" };
343363

344364
// Download libraries, cache and set SHERPA_LIB_PATH
345365

@@ -352,7 +372,8 @@ fn main() {
352372
debug_log!("Download binaries enabled");
353373
// debug_log!("Dist table: {:?}", DIST_TABLE.targets);
354374
// Try download sherpa libs and set SHERPA_LIB_PATH
355-
if let Some(dist) = DIST_TABLE.get(&target, is_dynamic) {
375+
if let Some(dist) = DIST_TABLE.get(&target, &mut is_dynamic) {
376+
debug_log!("is_dynamic after: {}", is_dynamic);
356377
optional_dist = Some(dist.clone());
357378
let mut cache_dir = if let Some(dir) = get_cache_dir() {
358379
dir.join(target.clone()).join(&dist.checksum)
@@ -365,8 +386,16 @@ fn main() {
365386
cache_dir = env::var("OUT_DIR").unwrap().into();
366387
}
367388
debug_log!("Cache dir: {}", cache_dir.display());
389+
368390
let lib_dir = cache_dir.join(&dist.name);
369-
if !lib_dir.exists() {
391+
// if is mobile then check if cache dir not empty
392+
let cache_dir_empty = cache_dir
393+
.read_dir()
394+
.map(|mut entries| entries.next().is_none())
395+
.unwrap_or(true);
396+
// Check if cache directory exists
397+
// Sherpa uses special directory structure for mobile
398+
if (!lib_dir.exists() && !is_mobile) || (is_mobile && cache_dir_empty) {
370399
let downloaded_file = fetch_file(&dist.url);
371400
let hash = sha256(&downloaded_file);
372401
// verify checksum
@@ -382,15 +411,39 @@ fn main() {
382411
}
383412

384413
// In Android, we need to set SHERPA_LIB_PATH to the cache directory sincie it has jniLibs
385-
if target.contains("android") || target.contains("ios") {
386-
env::set_var("SHERPA_LIB_PATH", cache_dir);
414+
if is_mobile {
415+
env::set_var("SHERPA_LIB_PATH", &cache_dir);
387416
} else {
388417
env::set_var("SHERPA_LIB_PATH", cache_dir.join(&dist.name));
389418
}
390419

391420
debug_log!("dist libs: {:?}", dist.libs);
392421
if let Some(libs) = dist.libs {
393-
sherpa_libs = libs;
422+
for lib in libs.iter() {
423+
let lib_path = cache_dir.join(lib);
424+
let lib_parent = lib_path.parent().unwrap();
425+
add_search_path(lib_parent);
426+
}
427+
428+
sherpa_libs = libs
429+
.iter()
430+
.map(|p| {
431+
Path::new(p)
432+
.file_name()
433+
.unwrap()
434+
.to_string_lossy()
435+
// Remove lib prefix
436+
.strip_prefix("lib")
437+
.unwrap_or_else(|| p)
438+
// Remove .so
439+
.replace(".so", "")
440+
// Remove .dylib
441+
.replace(".dylib", "")
442+
// Remove .a
443+
.replace(".a", "")
444+
.to_string()
445+
})
446+
.collect();
394447
} else {
395448
sherpa_libs = extract_lib_names(&lib_dir, is_dynamic, &target_os);
396449
}
@@ -403,70 +456,75 @@ fn main() {
403456
// Skip build if SHERPA_LIB_PATH specified
404457
debug_log!("Skpping build with Cmake...");
405458
debug_log!("SHERPA_LIB_PATH: {}", sherpa_lib_path);
406-
println!(
407-
"cargo:rustc-link-search={}",
408-
Path::new(&sherpa_lib_path).join("lib").display()
409-
);
410-
sherpa_libs = extract_lib_names(Path::new(&sherpa_lib_path), is_dynamic, &target_os);
459+
add_search_path(Path::new(&sherpa_lib_path).join("lib"));
460+
if sherpa_libs.is_empty() {
461+
sherpa_libs = extract_lib_names(Path::new(&sherpa_lib_path), is_dynamic, &target_os);
462+
}
411463
} else {
412464
// Build with CMake
413465
let bindings_dir = config.build();
414-
println!("cargo:rustc-link-search={}", bindings_dir.display());
466+
add_search_path(&bindings_dir);
415467

416468
// Extract libs on desktop platforms
417-
if !target.contains("android") && !target.contains("ios") {
469+
if !is_mobile {
418470
sherpa_libs = extract_lib_names(&bindings_dir, is_dynamic, &target_os);
419471
}
420472
}
421473

422474
// Search paths
423-
println!("cargo:rustc-link-search={}", out_dir.join("lib").display());
475+
debug_log!("Sherpa libs: {:?}", sherpa_libs);
476+
add_search_path(out_dir.join("lib"));
424477

425478
for lib in sherpa_libs {
426479
if lib.contains("cxx") {
427480
continue;
428481
}
429-
debug_log!(
430-
"LINK {}",
431-
format!("cargo:rustc-link-lib={}={}", sherpa_libs_kind, lib)
432-
);
433-
println!("cargo:rustc-link-lib={}={}", sherpa_libs_kind, lib);
482+
link_lib(&lib, is_dynamic);
434483
}
435484

436485
// Windows debug
437486
if cfg!(all(debug_assertions, windows)) {
438-
println!("cargo:rustc-link-lib=dylib=msvcrtd");
487+
link_lib("msvcrtd", true);
439488
}
440489

441490
// macOS
442-
if target_os == "macos" {
443-
println!("cargo:rustc-link-lib=framework=Foundation");
444-
println!("cargo:rustc-link-lib=c++");
491+
if target_os == "macos" || target_os == "ios" {
492+
link_framework("CoreML");
493+
link_framework("Foundation");
494+
link_lib("c++", true);
445495
}
446496

447497
// Linux
448498
if target_os == "linux" || target == "android" {
449-
println!("cargo:rustc-link-lib=dylib=stdc++");
499+
link_lib("stdc++", true);
450500
}
451501

452-
if target.contains("apple") {
502+
// macOS
503+
if target_os == "macos" {
453504
// On (older) OSX we need to link against the clang runtime,
454505
// which is hidden in some non-default path.
455506
//
456507
// More details at https://github.com/alexcrichton/curl-rust/issues/279.
457508
if let Some(path) = macos_link_search_path() {
458-
println!("cargo:rustc-link-lib=clang_rt.osx");
459-
println!("cargo:rustc-link-search={}", path);
509+
add_search_path(path);
510+
link_lib("clang_rt.osx", is_dynamic);
460511
}
461512
}
462513

514+
// TODO: add rpath for Android and iOS so it can find its dependencies in the same directory of executable
515+
// if is_mobile {
516+
// // Add rpath for Android and iOS so that the shared library can find its dependencies in the same directory as well
517+
// println!("cargo:rustc-link-arg=-Wl,-rpath,'$ORIGIN'");
518+
// }
519+
463520
// copy DLLs to target
464521
if is_dynamic {
465522
let mut libs_assets = extract_lib_assets(&out_dir, &target_os);
466523
if let Ok(sherpa_lib_path) = env::var("SHERPA_LIB_PATH") {
467524
libs_assets.extend(extract_lib_assets(Path::new(&sherpa_lib_path), &target_os));
468525
}
469526

527+
#[cfg(feature = "download-binaries")]
470528
if let Some(dist) = optional_dist {
471529
if let Some(assets) = dist.libs {
472530
if let Ok(sherpa_lib_path) = env::var("SHERPA_LIB_PATH") {
@@ -483,23 +541,23 @@ fn main() {
483541
let dst = target_dir.join(filename);
484542
// debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
485543
if !dst.exists() {
486-
hard_link(asset.clone(), dst);
544+
copy_file(asset.clone(), dst);
487545
}
488546

489547
// Copy DLLs to examples as well
490548
if target_dir.join("examples").exists() {
491549
let dst = target_dir.join("examples").join(filename);
492550
// debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
493551
if !dst.exists() {
494-
hard_link(asset.clone(), dst);
552+
copy_file(asset.clone(), dst);
495553
}
496554
}
497555

498556
// Copy DLLs to target/profile/deps as well for tests
499557
let dst = target_dir.join("deps").join(filename);
500558
// debug_log!("HARD LINK {} TO {}", asset.display(), dst.display());
501559
if !dst.exists() {
502-
hard_link(asset.clone(), dst);
560+
copy_file(asset.clone(), dst);
503561
}
504562
}
505563
}

crates/sherpa-rs-sys/dist.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"android": {
2222
"archive": "sherpa-onnx-{tag}-android.tar.bz2",
23+
"is_dynamic": true,
2324
"arch": {
2425
"aarch64-linux-android": "arm64-v8a",
2526
"x86_64-linux-android": "x86_64",
@@ -32,6 +33,7 @@
3233
},
3334
"ios": {
3435
"archive": "sherpa-onnx-{tag}-ios.tar.bz2",
36+
"is_dynamic": false,
3537
"arch": {
3638
"aarch64-apple-ios": "ios-arm64",
3739
"aarch64-apple-ios-sim": "ios-arm64-simulator"

crates/sherpa-rs-sys/src/download_binaries.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl DistTable {
134134
table
135135
}
136136

137-
pub fn get(&self, target: &str, is_dynamic: bool) -> Option<Dist> {
137+
pub fn get(&self, target: &str, is_dynamic: &mut bool) -> Option<Dist> {
138138
debug_log!("Extracting dist for target: {}", target);
139139
// debug_log!("dist table: {:?}", self);
140140
let target_dist = if target.contains("android") {
@@ -151,10 +151,14 @@ impl DistTable {
151151
serde_json::to_string(target_dist).unwrap()
152152
);
153153
let archive = if target_dist.get("archive").is_some() {
154+
// archive name
155+
// static/dynamic located in 'is_dynamic' field
154156
target_dist.get("archive").unwrap().as_str().unwrap()
155-
} else if is_dynamic {
157+
} else if *is_dynamic {
158+
// dynamic archive name
156159
target_dist.get("dynamic").unwrap().as_str().unwrap()
157160
} else {
161+
// static archive name
158162
target_dist.get("static").unwrap().as_str().unwrap()
159163
};
160164
let name = archive.replace(".tar.bz2", "");
@@ -181,6 +185,13 @@ impl DistTable {
181185
let url = self.url.replace("{archive}", archive);
182186
let checksum = DIST_CHECKSUM.get(archive).unwrap();
183187

188+
// modify is_dynamic
189+
debug_log!("checking is_dynamic");
190+
if let Some(target_dist) = target_dist.get("is_dynamic") {
191+
*is_dynamic = target_dist.as_bool().unwrap();
192+
debug_log!("is_dynamic: {}", *is_dynamic);
193+
}
194+
184195
let dist = Dist {
185196
url,
186197
name,

0 commit comments

Comments
 (0)