mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-16 15:34:08 +08:00
fix: package local onnx runtime for release
This commit is contained in:
@@ -277,6 +277,70 @@ jobs:
|
||||
echo "tauri.conf.headless.json version is now: $(node -e "console.log(JSON.parse(require('fs').readFileSync('tauri.conf.headless.json','utf8')).version)")"
|
||||
echo "createUpdaterArtifacts: $(node -e "console.log(JSON.parse(require('fs').readFileSync('tauri.conf.json','utf8')).bundle.createUpdaterArtifacts)")"
|
||||
|
||||
- name: Configure ONNX runtime bundling
|
||||
shell: bash
|
||||
env:
|
||||
TARGET_TRIPLE: ${{ matrix.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node - <<'NODE'
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const target = process.env.TARGET_TRIPLE;
|
||||
const srcTauri = path.resolve('src-tauri');
|
||||
const portablePath = (value) => value.replace(/\\/g, '/');
|
||||
const unique = (values) => [...new Set(values.filter(Boolean))];
|
||||
|
||||
if (target === 'x86_64-pc-windows-msvc') {
|
||||
const releaseDir = path.join(srcTauri, 'target', target, 'release');
|
||||
const dlls = ['onnxruntime.dll', 'sherpa-onnx-c-api.dll'];
|
||||
const hookPath = path.join(srcTauri, 'target', 'release-runtime-libs.nsh');
|
||||
fs.mkdirSync(path.dirname(hookPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
hookPath,
|
||||
[
|
||||
'!macro NSIS_HOOK_POSTINSTALL',
|
||||
' SetOutPath "$INSTDIR"',
|
||||
...dlls.map(
|
||||
(name) =>
|
||||
` File /a "/oname=${name}" "${portablePath(path.join(releaseDir, name))}"`,
|
||||
),
|
||||
'!macroend',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
const configPath = path.join(srcTauri, 'tauri.windows.conf.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
config.bundle = config.bundle || {};
|
||||
config.bundle.windows = config.bundle.windows || {};
|
||||
config.bundle.windows.nsis = config.bundle.windows.nsis || {};
|
||||
config.bundle.windows.nsis.installerHooks = hookPath;
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
||||
console.log(`Configured Windows ONNX runtime NSIS hook: ${hookPath}`);
|
||||
} else if (target.endsWith('apple-darwin')) {
|
||||
const configPath = path.join(srcTauri, 'tauri.conf.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
const frameworks = [
|
||||
'libonnxruntime.1.24.4.dylib',
|
||||
'libonnxruntime.dylib',
|
||||
'libsherpa-onnx-c-api.dylib',
|
||||
].map((name) => portablePath(path.join(srcTauri, 'target', target, 'release', name)));
|
||||
|
||||
config.bundle = config.bundle || {};
|
||||
config.bundle.macOS = config.bundle.macOS || {};
|
||||
config.bundle.macOS.frameworks = unique([
|
||||
...(config.bundle.macOS.frameworks || []),
|
||||
...frameworks,
|
||||
]);
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
||||
console.log(`Configured macOS ONNX runtime frameworks for ${target}`);
|
||||
} else {
|
||||
console.log(`No ONNX runtime bundling override needed for ${target}`);
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Resolve updater endpoint
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -480,6 +544,27 @@ jobs:
|
||||
# 默认不启用 voice feature(包含 whisper-rs,编译很慢)
|
||||
args: --target ${{ matrix.target }} --config tauri.windows.conf.json
|
||||
|
||||
- name: Verify packaged ONNX runtime libraries (Windows)
|
||||
if: matrix.platform == 'windows-2022'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_DIR="src-tauri/target/${{ matrix.target }}/release"
|
||||
for dll in onnxruntime.dll sherpa-onnx-c-api.dll; do
|
||||
if [ ! -f "$RELEASE_DIR/$dll" ]; then
|
||||
echo "::error::Expected ONNX runtime DLL missing: $RELEASE_DIR/$dll"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
HOOK_FILE="src-tauri/target/release-runtime-libs.nsh"
|
||||
if [ ! -f "$HOOK_FILE" ]; then
|
||||
echo "::error::Expected NSIS runtime hook missing: $HOOK_FILE"
|
||||
exit 1
|
||||
fi
|
||||
grep -F 'onnxruntime.dll' "$HOOK_FILE"
|
||||
grep -F 'sherpa-onnx-c-api.dll' "$HOOK_FILE"
|
||||
|
||||
- name: Stage Windows release assets
|
||||
if: matrix.platform == 'windows-2022'
|
||||
shell: bash
|
||||
@@ -538,6 +623,7 @@ jobs:
|
||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TARGET_DIR: src-tauri/target
|
||||
RUSTFLAGS: "-C link-arg=-Wl,-rpath,@executable_path/../Frameworks"
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: sccache
|
||||
# Apple 签名环境变量
|
||||
@@ -589,6 +675,7 @@ jobs:
|
||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TARGET_DIR: src-tauri/target
|
||||
RUSTFLAGS: "-C link-arg=-Wl,-rpath,@executable_path/../Frameworks"
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: sccache
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -685,6 +772,27 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "Validating notarized app bundle: $app_bundle"
|
||||
FRAMEWORKS_DIR="$app_bundle/Contents/Frameworks"
|
||||
for dylib in libonnxruntime.1.24.4.dylib libonnxruntime.dylib libsherpa-onnx-c-api.dylib; do
|
||||
if [ ! -f "$FRAMEWORKS_DIR/$dylib" ]; then
|
||||
echo "::error::Expected ONNX runtime dylib missing: $FRAMEWORKS_DIR/$dylib"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
MAIN_BINARY="$app_bundle/Contents/MacOS/Lime"
|
||||
if [ ! -f "$MAIN_BINARY" ]; then
|
||||
MAIN_BINARY="$(find "$app_bundle/Contents/MacOS" -maxdepth 1 -type f | sort | head -n 1)"
|
||||
fi
|
||||
if [ -z "$MAIN_BINARY" ] || [ ! -f "$MAIN_BINARY" ]; then
|
||||
echo "::error::Unable to resolve macOS app main binary under $app_bundle/Contents/MacOS"
|
||||
exit 1
|
||||
fi
|
||||
if ! otool -l "$MAIN_BINARY" | grep -F '@executable_path/../Frameworks' >/dev/null; then
|
||||
echo "::error::macOS app binary is missing ONNX runtime Frameworks rpath: $MAIN_BINARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
spctl -a -vv "$app_bundle"
|
||||
xcrun stapler validate "$app_bundle"
|
||||
|
||||
|
||||
Generated
+11
-18
@@ -4227,12 +4227,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha256"
|
||||
version = "1.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
@@ -5198,6 +5192,16 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
@@ -5876,12 +5880,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust2"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e20f57f9918e5bd7bc58c22cdd70a6afc7375d4dd9683af5f2b34bd3d2bba619"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -7160,11 +7158,11 @@ version = "2.0.0-rc.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133"
|
||||
dependencies = [
|
||||
"libloading 0.9.0",
|
||||
"ndarray",
|
||||
"ort-sys",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ureq 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7172,11 +7170,6 @@ name = "ort-sys"
|
||||
version = "2.0.0-rc.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90"
|
||||
dependencies = [
|
||||
"hmac-sha256",
|
||||
"lzma-rust2",
|
||||
"ureq 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
|
||||
@@ -21,7 +21,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# 本地 ONNX 嵌入
|
||||
dirs = { workspace = true, optional = true }
|
||||
fastembed = { version = "5.13.4", default-features = false, features = ["hf-hub-native-tls", "ort-download-binaries-native-tls"], optional = true }
|
||||
fastembed = { version = "5.13.4", default-features = false, features = ["hf-hub-native-tls", "ort-load-dynamic"], optional = true }
|
||||
|
||||
# 错误处理
|
||||
anyhow = "1"
|
||||
|
||||
@@ -27,6 +27,8 @@ const LOCAL_ONNX_HF_ENDPOINT_ENV: &str = "LIME_LOCAL_ONNX_HF_ENDPOINT";
|
||||
const LOCAL_ONNX_MODEL_REPO: &str = "Qdrant/all-MiniLM-L6-v2-onnx";
|
||||
#[cfg(feature = "local-onnx")]
|
||||
const LOCAL_ONNX_MODEL_REVISION: &str = "main";
|
||||
#[cfg(feature = "local-onnx")]
|
||||
const ORT_DYLIB_PATH_ENV: &str = "ORT_DYLIB_PATH";
|
||||
|
||||
/// OpenAI Embedding API 请求
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -131,6 +133,54 @@ fn local_onnx_hf_endpoint() -> String {
|
||||
.unwrap_or_else(|| "https://huggingface.co".to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
fn ort_dylib_name() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"onnxruntime.dll"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"libonnxruntime.dylib"
|
||||
} else {
|
||||
"libonnxruntime.so"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
fn resolve_packaged_ort_dylib_path_from_exe(exe_path: &Path) -> Option<PathBuf> {
|
||||
let exe_dir = exe_path.parent()?;
|
||||
let dylib_name = ort_dylib_name();
|
||||
|
||||
let next_to_exe = exe_dir.join(dylib_name);
|
||||
if next_to_exe.is_file() {
|
||||
return Some(next_to_exe);
|
||||
}
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
let frameworks_dir = exe_dir.parent()?.join("Frameworks");
|
||||
let bundled_framework = frameworks_dir.join(dylib_name);
|
||||
if bundled_framework.is_file() {
|
||||
return Some(bundled_framework);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
fn configure_local_onnx_runtime_library_path() {
|
||||
if std::env::var_os(ORT_DYLIB_PATH_ENV).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(path) = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|exe_path| resolve_packaged_ort_dylib_path_from_exe(&exe_path))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
std::env::set_var(ORT_DYLIB_PATH_ENV, path);
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
fn local_onnx_manual_cache_path(cache_dir: &Path, file: &str) -> PathBuf {
|
||||
let mut path = cache_dir
|
||||
@@ -215,6 +265,8 @@ fn build_local_onnx_model_from_manual_cache(
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
fn build_local_onnx_model(spec: &LocalOnnxModelSpec) -> Result<TextEmbedding, String> {
|
||||
configure_local_onnx_runtime_library_path();
|
||||
|
||||
let cache_dir = local_onnx_cache_dir();
|
||||
std::fs::create_dir_all(&cache_dir)
|
||||
.map_err(|e| format!("创建本地 ONNX 嵌入模型缓存目录失败: {e}"))?;
|
||||
@@ -564,6 +616,53 @@ mod tests {
|
||||
assert!(error.contains("不支持的本地 ONNX 嵌入模型"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
#[test]
|
||||
fn test_resolve_packaged_ort_dylib_path_from_exe() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"lime-embedding-ort-path-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
let exe_dir = if cfg!(target_os = "macos") {
|
||||
root.join("Contents").join("MacOS")
|
||||
} else {
|
||||
root.clone()
|
||||
};
|
||||
std::fs::create_dir_all(&exe_dir).unwrap();
|
||||
|
||||
let exe_path = exe_dir.join(if cfg!(target_os = "windows") {
|
||||
"Lime.exe"
|
||||
} else {
|
||||
"lime"
|
||||
});
|
||||
let next_to_exe = exe_dir.join(ort_dylib_name());
|
||||
std::fs::write(&next_to_exe, []).unwrap();
|
||||
assert_eq!(
|
||||
resolve_packaged_ort_dylib_path_from_exe(&exe_path),
|
||||
Some(next_to_exe.clone())
|
||||
);
|
||||
|
||||
std::fs::remove_file(&next_to_exe).unwrap();
|
||||
if cfg!(target_os = "macos") {
|
||||
let frameworks_dir = root.join("Contents").join("Frameworks");
|
||||
std::fs::create_dir_all(&frameworks_dir).unwrap();
|
||||
let framework_dylib = frameworks_dir.join(ort_dylib_name());
|
||||
std::fs::write(&framework_dylib, []).unwrap();
|
||||
assert_eq!(
|
||||
resolve_packaged_ort_dylib_path_from_exe(&exe_path),
|
||||
Some(framework_dylib)
|
||||
);
|
||||
} else {
|
||||
assert_eq!(resolve_packaged_ort_dylib_path_from_exe(&exe_path), None);
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-onnx")]
|
||||
#[tokio::test]
|
||||
#[ignore = "真实模型下载和本地 ONNX 推理测试:设置 LIME_REAL_API_TEST=1 后执行"]
|
||||
|
||||
@@ -21,7 +21,7 @@ cpal = "0.15"
|
||||
whisper-rs = { version = "0.12", optional = true }
|
||||
|
||||
# SenseVoice 本地识别(可选,链接 sherpa-onnx 运行时,不内置模型文件)
|
||||
sherpa-onnx = { version = "1.13", default-features = false, features = ["static"], optional = true }
|
||||
sherpa-onnx = { version = "1.13", default-features = false, features = ["shared"], optional = true }
|
||||
|
||||
# WAV 处理
|
||||
hound = "3.5"
|
||||
|
||||
Reference in New Issue
Block a user