Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ codegen-units = 1 # better LTO at cost of build time
panic = "abort" # smaller binary, no unwind tables
```

Final binary size: ~80 MB on Linux, ~100 MB on Windows (sherpa-onnx
ships its DLLs alongside, see the Cargo.toml comment for why), ~60 MB
on macOS. About half of that is the statically-linked CT2 runtime.
Final binary size: ~80 MB on Linux, ~100 MB on Windows and macOS
(sherpa-onnx ships shared runtime libraries alongside, see the
Cargo.toml comments for why). About half of that is the statically-
linked CT2 runtime.

## Cross-compilation

Expand Down Expand Up @@ -134,8 +135,8 @@ production build matrix.
- `src/index.html`, `src/main.js`, `src/styles.css` — settings + dashboard UI.
- `src/overlay.html`, `src/overlay.js` — listening waveform overlay.
- `scripts/stage-bundle-resources.cjs` — staging script the Tauri
bundler invokes via `beforeBundleCommand` to copy Windows DLLs into
the resources dir.
bundler invokes via `beforeBundleCommand` to copy sherpa-onnx /
ONNX Runtime shared libraries into the resources dir.

## Releasing

Expand Down
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ backends, cross-platform installers.

### Tech notes

- Final binary size: ~80 MB Linux, ~100 MB Windows (sherpa-onnx
ships its DLLs alongside on Windows due to a prebuilt-vs-local
MSVC ABI mismatch — see Cargo.toml comment), ~60 MB macOS.
- Final binary size: ~80 MB Linux, ~100 MB Windows and macOS
(sherpa-onnx ships shared runtime libraries alongside due to
platform-specific static-link issues — see Cargo.toml comments).
- First clean build: ~25–35 min (CT2 C++ compile dominates).
Incremental: ~30 s. CI uses `Swatinem/rust-cache` to skip the
cold path on subsequent runs.
Expand Down
82 changes: 56 additions & 26 deletions scripts/stage-bundle-resources.cjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env node
// Stage runtime DLLs for the Tauri bundler.
// Stage runtime shared libraries for the Tauri bundler.
//
// On Windows we link sherpa-onnx in `shared` mode (see Cargo.toml
// for the MSVC ABI rationale), which means the final installer
// has to ship `sherpa-onnx-c-api.dll`, `onnxruntime.dll`, and a
// few siblings next to `vibe-to-text.exe`.
// few siblings next to `vibe-to-text.exe`. macOS ships the matching
// `.dylib` files next to the app executable.
//
// `sherpa-onnx-sys`'s build script copies them into
// `src-tauri/target/<profile>/` so `cargo run` works in dev. For
Expand All @@ -14,14 +15,15 @@
// `target/release/` doesn't exist before the link step.
//
// This script runs as a Tauri `beforeBundleCommand` and copies the
// DLLs from `target/<profile>/` into `src-tauri/bundle-resources/`,
// where the resources glob picks them up.
// shared libraries from `target/<profile>/` into
// `src-tauri/bundle-resources/`, where the bundle config picks them up.
//
// Cross-platform: on macOS / Linux we use sherpa-onnx in `static`
// mode — there's nothing to stage and this script exits quietly.
// Cross-platform: on macOS, Linux, and Windows we use sherpa-onnx
// in `shared` mode and stage the platform runtime libraries here.

const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");

const repoRoot = path.resolve(__dirname, "..");
const tauriDir = path.join(repoRoot, "src-tauri");
Expand All @@ -31,20 +33,9 @@ const stageDir = path.join(tauriDir, "bundle-resources");
// missing-dir validation.
fs.mkdirSync(stageDir, { recursive: true });

// macOS uses `static` sherpa-onnx (clean ABI, single-file .app
// bundle). Windows + Linux use `shared` because sherpa-onnx-sys's
// prebuilt static libs ship their own copy of protobuf which
// collides with sentencepiece-sys's protobuf at link time. On
// shared builds, sherpa-onnx-sys's build script copies the runtime
// libs into target/<profile>/; we re-stage them here so Tauri's
// `bundle.resources` glob can pick them up alongside the executable
// in the installer.
if (process.platform === "darwin") {
console.log(
"stage-bundle-resources: skipping on darwin (sherpa-onnx is static here)."
);
process.exit(0);
}
// sherpa-onnx-sys's build script copies the runtime libs into
// target/<profile>/; we re-stage them here so Tauri can pick them
// up alongside the executable in the installer.

// Tauri sets TAURI_ENV_DEBUG=true for `tauri dev` and false (or
// unset) for `tauri build`. Match that to the cargo profile.
Expand All @@ -54,16 +45,24 @@ const profile = process.env.TAURI_ENV_DEBUG === "true" ? "debug" : "release";
// rather than `target/<profile>/`. Tauri exposes the active triple
// as TAURI_ENV_TARGET_TRIPLE.
const triple = process.env.TAURI_ENV_TARGET_TRIPLE || "";
const targetDir = triple
const preferredTargetDir = triple
? path.join(tauriDir, "target", triple, profile)
: path.join(tauriDir, "target", profile);
const fallbackTargetDir = path.join(tauriDir, "target", profile);
const targetDir = fs.existsSync(preferredTargetDir)
? preferredTargetDir
: fallbackTargetDir;

// The list sherpa-onnx-sys's build script emits when the `shared`
// feature is on. The actual file extension + naming differs per
// platform: `.dll` on Windows, `.so` (with optional version
// suffixes like `.so.1.13.0`) on Linux. We glob for sensible name
// patterns rather than hard-code each file.
// platform: `.dll` on Windows, `.dylib` on macOS, `.so` (with
// optional version suffixes like `.so.1.13.0`) on Linux. We glob for
// sensible name patterns rather than hard-code each file.
const PLATFORM_PATTERNS = {
darwin: [
/^libsherpa-onnx.*\.dylib$/i,
/^libonnxruntime.*\.dylib$/i,
],
win32: [
/^sherpa-onnx.*\.dll$/i,
/^onnxruntime.*\.dll$/i,
Expand All @@ -83,15 +82,21 @@ if (!patterns) {
}

if (!fs.existsSync(targetDir)) {
const cargoBuildHint =
profile === "release" ? "cargo build --release" : "cargo build";
console.error(
`stage-bundle-resources: targetDir ${targetDir} doesn't exist.\n` +
` Run \`cargo build --release\` first.`
` Run \`${cargoBuildHint}\` first.`
);
process.exit(1);
}

const entries = fs.readdirSync(targetDir);
let staged = 0;
for (const name of fs.readdirSync(stageDir)) {
if (!patterns.some((re) => re.test(name))) continue;
fs.rmSync(path.join(stageDir, name), { force: true });
}
for (const name of entries) {
if (!patterns.some((re) => re.test(name))) continue;
const src = path.join(targetDir, name);
Expand All @@ -105,7 +110,11 @@ if (staged === 0) {
console.error(
`stage-bundle-resources: no matching shared libs found in ${targetDir}.\n` +
` Expected sherpa-onnx + onnxruntime ${
process.platform === "win32" ? "DLLs" : ".so files"
process.platform === "win32"
? "DLLs"
: process.platform === "darwin"
? ".dylib files"
: ".so files"
}.\n` +
` Confirm the sherpa-onnx \`shared\` feature is enabled.`
);
Expand All @@ -114,3 +123,24 @@ if (staged === 0) {
console.log(
`stage-bundle-resources: staged ${staged} shared lib(s) from ${targetDir} → ${stageDir}`
);

if (process.platform === "darwin") {
const executable = path.join(targetDir, "vibe-to-text");
if (fs.existsSync(executable)) {
const result = spawnSync(
"install_name_tool",
["-add_rpath", "@executable_path", executable],
{ encoding: "utf8" }
);
if (result.status !== 0) {
const stderr = result.stderr || "";
if (!stderr.includes("would duplicate path")) {
console.error(stderr.trim());
process.exit(result.status || 1);
}
}
console.log(
`stage-bundle-resources: ensured @executable_path rpath on ${executable}`
);
}
}
7 changes: 4 additions & 3 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ ct2rs = { version = "0.9", default-features = false, features = [
"ruy",
"accelerate",
] }
# sherpa-onnx static — Apple's toolchain has a stable ABI, k2-fsa's
# prebuilt macOS static libs link cleanly. Single-file .app bundle.
sherpa-onnx = { version = "1.13", default-features = false, features = ["static"] }
# sherpa-onnx shared — matches Windows/Linux and avoids uncaught C++
# exceptions crossing the Rust/static-library boundary during ONNX
# Runtime session creation on macOS.
sherpa-onnx = { version = "1.13", default-features = false, features = ["shared"] }

# --- Linux: same CUDA-dynamic-loading story as Windows. ---
# We could enable `openmp-runtime-comp` here (gomp.lib ships with
Expand Down
39 changes: 35 additions & 4 deletions src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
// Tauri's standard build hook. Whisper-rs statically links whisper.cpp
// into the binary at build time (via CMake), so we no longer need to
// shuffle external DLLs (onnxruntime.dll, moonshine.dll) next to the
// exe — those were Moonshine-era artifacts and have been removed.
use std::{env, fs, path::PathBuf};

// Tauri validates every bundle.resources source path while compiling
// this build script, before `beforeBundleCommand` can stage the real
// shared libraries. Keep zero-byte placeholders in place so local
// macOS / Linux `cargo check`, `tauri dev`, and `tauri build` follow
// the same path as CI. scripts/stage-bundle-resources.cjs overwrites
// these files with the runtime libraries before the installer is
// created.
const BUNDLE_RESOURCE_STUBS: &[&str] = &[
"sherpa-onnx-c-api.dll",
"sherpa-onnx-cxx-api.dll",
"onnxruntime.dll",
"onnxruntime_providers_shared.dll",
"libsherpa-onnx-c-api.dylib",
"libsherpa-onnx-cxx-api.dylib",
"libonnxruntime.dylib",
"libonnxruntime.1.24.4.dylib",
];

fn main() {
ensure_bundle_resource_stubs();
tauri_build::build();
}

fn ensure_bundle_resource_stubs() {
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by cargo"));
let resource_dir = manifest_dir.join("bundle-resources");

fs::create_dir_all(&resource_dir).expect("create bundle resource staging dir");
for name in BUNDLE_RESOURCE_STUBS {
let path = resource_dir.join(name);
if !path.exists() {
fs::File::create(&path).expect("create bundle resource validation stub");
}
}
}
8 changes: 7 additions & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@
}
},
"macOS": {
"minimumSystemVersion": "11.0"
"minimumSystemVersion": "11.0",
"files": {
"MacOS/libsherpa-onnx-c-api.dylib": "bundle-resources/libsherpa-onnx-c-api.dylib",
"MacOS/libsherpa-onnx-cxx-api.dylib": "bundle-resources/libsherpa-onnx-cxx-api.dylib",
"MacOS/libonnxruntime.dylib": "bundle-resources/libonnxruntime.dylib",
"MacOS/libonnxruntime.1.24.4.dylib": "bundle-resources/libonnxruntime.1.24.4.dylib"
}
},
"linux": {
"deb": {
Expand Down
Loading