This commit is contained in:
949036910@qq.com
2026-06-02 22:48:18 +08:00
parent f091e17ca4
commit f9bccba872
21 changed files with 2386 additions and 68 deletions

View File

@@ -1,5 +1,7 @@
//! 封面 Python 运行时与脚本目录定位(对齐 Electron bundled python-runtime
//! 封面 Python 运行时定位bundled `resources/pythonruntime/python.exe`
//! 封面脚本由服务端下发,不在此目录打包。
use std::collections::HashMap;
use std::path::{Path, PathBuf};
fn exe_name() -> &'static str {
@@ -10,7 +12,43 @@ fn exe_name() -> &'static str {
}
}
/// bundled `python-runtimebackup` 或系统 / 环境变量 Python。
fn pythonruntime_candidates() -> Vec<PathBuf> {
let mut out = Vec::new();
// 开发源码树优先target/debug/resources 可能是残缺副本(缺 PIL 等)
out.push(
PathBuf::from("src-tauri/resources/pythonruntime").join(exe_name()),
);
out.push(PathBuf::from("resources/pythonruntime").join(exe_name()));
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
out.push(
dir.join("resources")
.join("pythonruntime")
.join(exe_name()),
);
}
}
out
}
/// `python.exe` 所在目录即 PYTHONHOME含 Lib/site-packages
pub fn python_home(python_exe: &Path) -> PathBuf {
python_exe
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from("."))
}
fn site_packages_dir(python_exe: &Path) -> PathBuf {
python_home(python_exe).join("Lib").join("site-packages")
}
fn runtime_has_pillow(python_exe: &Path) -> bool {
let site = site_packages_dir(python_exe);
site.join("PIL").is_dir() || site.join("Pillow").is_dir()
}
/// bundled `resources/pythonruntime/python.exe` 或 `AICLIENT_PYTHON_PATH`。
pub fn locate_python() -> Option<PathBuf> {
if let Ok(p) = std::env::var("AICLIENT_PYTHON_PATH") {
let pb = PathBuf::from(&p);
@@ -18,35 +56,46 @@ pub fn locate_python() -> Option<PathBuf> {
return Some(pb);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let bundled = dir
.join("resources")
.join("resources-bundles")
.join("python-runtimebackup")
.join(exe_name());
if bundled.exists() {
return Some(bundled);
}
let cover_only = dir.join("resources").join("cover-python").join(exe_name());
if cover_only.exists() {
return Some(cover_only);
}
for path in pythonruntime_candidates() {
if path.is_file() && runtime_has_pillow(&path) {
return Some(path);
}
}
let dev_bundled = PathBuf::from("src-tauri/resources/resources-bundles/python-runtimebackup")
.join(exe_name());
if dev_bundled.exists() {
return Some(dev_bundled);
}
let dev_cover = PathBuf::from("src-tauri/resources/cover-python").join(exe_name());
if dev_cover.exists() {
return Some(dev_cover);
for path in pythonruntime_candidates() {
if path.is_file() {
return Some(path);
}
}
None
}
/// 封面脚本目录(`advanced_cover_generator.py` 等)。
/// 为 bundled Python 子进程设置 PYTHONHOME / PYTHONPATH否则找不到 PIL/cv2)。
pub fn apply_python_runtime_env(
env: &mut HashMap<String, String>,
python_exe: &Path,
bundle_dir: Option<&Path>,
) {
let home = python_home(python_exe);
env.insert(
"PYTHONHOME".into(),
home.to_string_lossy().into_owned(),
);
let site = site_packages_dir(python_exe);
let sep = if cfg!(windows) { ";" } else { ":" };
let mut paths = vec![site.to_string_lossy().into_owned()];
if let Some(dir) = bundle_dir {
paths.push(dir.to_string_lossy().into_owned());
}
env.insert("PYTHONPATH".into(), paths.join(sep));
env.remove("PYTHONUSERBASE");
env.insert("PYTHONNOUSERSITE".into(), "1".into());
env.insert(
"AICLIENT_PYTHON_HOME".into(),
home.to_string_lossy().into_owned(),
);
}
/// 开发期本地脚本目录(发布构建由服务端拉取,不依赖此路径)。
pub fn locate_cover_scripts_dir() -> Option<PathBuf> {
if let Ok(p) = std::env::var("AICLIENT_COVER_SCRIPTS_DIR") {
let pb = PathBuf::from(&p);
@@ -54,18 +103,6 @@ pub fn locate_cover_scripts_dir() -> Option<PathBuf> {
return Some(pb);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let bundled = dir.join("resources").join("cover-python");
if bundled.join("advanced_cover_generator.py").is_file() {
return Some(bundled);
}
}
}
let dev = PathBuf::from("src-tauri/resources/cover-python");
if dev.join("advanced_cover_generator.py").is_file() {
return Some(dev);
}
let backend = PathBuf::from("../pythonbackend/scripts/cover");
if backend.join("advanced_cover_generator.py").is_file() {
return backend.canonicalize().ok();
@@ -89,10 +126,10 @@ mod tests {
use super::*;
#[test]
fn cover_scripts_dev_path_exists() {
let dev = Path::new("src-tauri/resources/cover-python/advanced_cover_generator.py");
fn pythonruntime_dev_path() {
let dev = PathBuf::from("src-tauri/resources/pythonruntime/python.exe");
if dev.is_file() {
assert!(locate_cover_scripts_dir().is_some());
assert!(locate_python().is_some());
}
}
}