111
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user