393 lines
13 KiB
Rust
393 lines
13 KiB
Rust
/**
|
||
* Python环境管理命令
|
||
* 用于管理Python环境和依赖
|
||
*/
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use tauri::{command, AppHandle};
|
||
use std::process::Command;
|
||
use std::path::PathBuf;
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct PythonEnvInfo {
|
||
pub python_type: String,
|
||
pub python_path: Option<String>,
|
||
pub version: Option<String>,
|
||
pub is_available: bool,
|
||
pub error: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct PythonPackage {
|
||
pub name: String,
|
||
pub version: String,
|
||
pub location: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct PythonEnvStatus {
|
||
pub embedded: PythonEnvInfo,
|
||
pub system: PythonEnvInfo,
|
||
pub current_env: String,
|
||
pub packages: Vec<PythonPackage>,
|
||
}
|
||
|
||
/// 获取嵌入式Python路径
|
||
fn get_embedded_python_path() -> Option<PathBuf> {
|
||
let python_exe = if cfg!(target_os = "windows") {
|
||
"python.exe"
|
||
} else {
|
||
"python3"
|
||
};
|
||
|
||
let possible_paths = vec![
|
||
std::env::current_dir().ok()
|
||
.map(|p| p.join("src-tauri").join("python-embed").join(python_exe)),
|
||
std::env::current_exe().ok()
|
||
.and_then(|p| p.parent().map(|p| p.join("python-embed").join(python_exe))),
|
||
];
|
||
|
||
for path in possible_paths.into_iter().flatten() {
|
||
if path.exists() {
|
||
return Some(path);
|
||
}
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// 检查Python环境信息
|
||
fn check_python_env(python_path: &str) -> PythonEnvInfo {
|
||
let mut cmd = Command::new(python_path);
|
||
cmd.arg("--version");
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||
PythonEnvInfo {
|
||
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
|
||
python_path: Some(python_path.to_string()),
|
||
version: Some(version),
|
||
is_available: true,
|
||
error: None,
|
||
}
|
||
} else {
|
||
let error = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||
PythonEnvInfo {
|
||
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
|
||
python_path: Some(python_path.to_string()),
|
||
version: None,
|
||
is_available: false,
|
||
error: Some(error),
|
||
}
|
||
}
|
||
}
|
||
Err(e) => PythonEnvInfo {
|
||
python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() },
|
||
python_path: Some(python_path.to_string()),
|
||
version: None,
|
||
is_available: false,
|
||
error: Some(e.to_string()),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 获取已安装的Python包列表
|
||
fn get_installed_packages(python_path: &str) -> Vec<PythonPackage> {
|
||
let mut cmd = Command::new(python_path);
|
||
cmd.args(&["-m", "pip", "list", "--format=json"]);
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
match serde_json::from_str::<Vec<serde_json::Value>>(&stdout) {
|
||
Ok(packages) => {
|
||
packages.into_iter().filter_map(|pkg| {
|
||
let name = pkg.get("name")?.as_str()?.to_string();
|
||
let version = pkg.get("version")?.as_str()?.to_string();
|
||
Some(PythonPackage {
|
||
name,
|
||
version,
|
||
location: None,
|
||
})
|
||
}).collect()
|
||
}
|
||
Err(_) => Vec::new(),
|
||
}
|
||
} else {
|
||
Vec::new()
|
||
}
|
||
}
|
||
Err(_) => Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 获取Python环境状态
|
||
#[command]
|
||
pub async fn get_python_env_status(_app: AppHandle) -> Result<PythonEnvStatus, String> {
|
||
println!("Getting Python environment status...");
|
||
|
||
// 检查嵌入式Python
|
||
let embedded_info = if let Some(embedded_path) = get_embedded_python_path() {
|
||
check_python_env(&embedded_path.to_string_lossy())
|
||
} else {
|
||
PythonEnvInfo {
|
||
python_type: "embedded".to_string(),
|
||
python_path: None,
|
||
version: None,
|
||
is_available: false,
|
||
error: Some("Embedded Python not found".to_string()),
|
||
}
|
||
};
|
||
|
||
// 检查系统Python
|
||
let system_info = check_python_env("python");
|
||
|
||
// 确定当前使用的环境
|
||
let current_env = if embedded_info.is_available {
|
||
"embedded".to_string()
|
||
} else if system_info.is_available {
|
||
"system".to_string()
|
||
} else {
|
||
"none".to_string()
|
||
};
|
||
|
||
// 获取当前环境的包列表
|
||
let packages = if embedded_info.is_available {
|
||
if let Some(ref path) = embedded_info.python_path {
|
||
get_installed_packages(path)
|
||
} else {
|
||
Vec::new()
|
||
}
|
||
} else if system_info.is_available {
|
||
get_installed_packages("python")
|
||
} else {
|
||
Vec::new()
|
||
};
|
||
|
||
Ok(PythonEnvStatus {
|
||
embedded: embedded_info,
|
||
system: system_info,
|
||
current_env,
|
||
packages,
|
||
})
|
||
}
|
||
|
||
/// 安装Python包
|
||
#[command]
|
||
pub async fn install_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
|
||
println!("Installing Python package: {}", package_name);
|
||
|
||
// 确定使用哪个Python
|
||
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
|
||
embedded_path.to_string_lossy().to_string()
|
||
} else {
|
||
"python".to_string()
|
||
};
|
||
|
||
let mut cmd = Command::new(&python_path);
|
||
cmd.args(&["-m", "pip", "install", &package_name]);
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
Ok(format!("Successfully installed {}: {}", package_name, stdout))
|
||
} else {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
Err(format!("Failed to install {}: {}", package_name, stderr))
|
||
}
|
||
}
|
||
Err(e) => Err(format!("Failed to execute pip install: {}", e))
|
||
}
|
||
}
|
||
|
||
/// 卸载Python包
|
||
#[command]
|
||
pub async fn uninstall_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
|
||
println!("Uninstalling Python package: {}", package_name);
|
||
|
||
// 确定使用哪个Python
|
||
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
|
||
embedded_path.to_string_lossy().to_string()
|
||
} else {
|
||
"python".to_string()
|
||
};
|
||
|
||
let mut cmd = Command::new(&python_path);
|
||
cmd.args(&["-m", "pip", "uninstall", "-y", &package_name]);
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
Ok(format!("Successfully uninstalled {}: {}", package_name, stdout))
|
||
} else {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
Err(format!("Failed to uninstall {}: {}", package_name, stderr))
|
||
}
|
||
}
|
||
Err(e) => Err(format!("Failed to execute pip uninstall: {}", e))
|
||
}
|
||
}
|
||
|
||
/// 升级Python包
|
||
#[command]
|
||
pub async fn upgrade_python_package(_app: AppHandle, package_name: String) -> Result<String, String> {
|
||
println!("Upgrading Python package: {}", package_name);
|
||
|
||
// 确定使用哪个Python
|
||
let python_path = if let Some(embedded_path) = get_embedded_python_path() {
|
||
embedded_path.to_string_lossy().to_string()
|
||
} else {
|
||
"python".to_string()
|
||
};
|
||
|
||
let mut cmd = Command::new(&python_path);
|
||
cmd.args(&["-m", "pip", "install", "--upgrade", &package_name]);
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
Ok(format!("Successfully upgraded {}: {}", package_name, stdout))
|
||
} else {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
Err(format!("Failed to upgrade {}: {}", package_name, stderr))
|
||
}
|
||
}
|
||
Err(e) => Err(format!("Failed to execute pip upgrade: {}", e))
|
||
}
|
||
}
|
||
|
||
/// 设置嵌入式Python环境
|
||
#[command]
|
||
pub async fn setup_embedded_python(_app: AppHandle) -> Result<String, String> {
|
||
println!("Setting up embedded Python environment...");
|
||
|
||
// 尝试多个可能的脚本路径
|
||
let possible_script_paths = vec![
|
||
// 开发环境:从项目根目录
|
||
std::env::current_dir().ok()
|
||
.map(|p| p.join("scripts").join("setup_embedded_python.py")),
|
||
// 相对路径
|
||
Some(std::path::PathBuf::from("scripts/setup_embedded_python.py")),
|
||
Some(std::path::PathBuf::from("./scripts/setup_embedded_python.py")),
|
||
// 绝对路径(如果在scripts目录中运行)
|
||
Some(std::path::PathBuf::from("setup_embedded_python.py")),
|
||
];
|
||
|
||
let mut script_path = None;
|
||
for path in possible_script_paths.into_iter().flatten() {
|
||
println!("Checking script path: {:?}", path);
|
||
if path.exists() {
|
||
script_path = Some(path);
|
||
break;
|
||
}
|
||
}
|
||
|
||
let script_path = script_path.ok_or_else(|| {
|
||
let current_dir = std::env::current_dir().unwrap_or_default();
|
||
format!(
|
||
"Setup script not found. Current directory: {:?}. Please ensure scripts/setup_embedded_python.py exists.",
|
||
current_dir
|
||
)
|
||
})?;
|
||
|
||
println!("Using script path: {:?}", script_path);
|
||
|
||
// 尝试多个Python命令
|
||
let python_commands = vec!["python", "python3", "py"];
|
||
let mut last_error = String::new();
|
||
|
||
for python_cmd in python_commands {
|
||
println!("Trying Python command: {}", python_cmd);
|
||
|
||
let mut cmd = Command::new(python_cmd);
|
||
cmd.arg(&script_path);
|
||
cmd.current_dir(std::env::current_dir().unwrap_or_default());
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
if output.status.success() {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
return Ok(format!("Embedded Python setup completed using {}: {}", python_cmd, stdout));
|
||
} else {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
last_error = format!("Failed with {}: {}", python_cmd, stderr);
|
||
println!("{}", last_error);
|
||
}
|
||
}
|
||
Err(e) => {
|
||
last_error = format!("Failed to execute {} {}: {}", python_cmd, script_path.display(), e);
|
||
println!("{}", last_error);
|
||
}
|
||
}
|
||
}
|
||
|
||
Err(format!("All Python commands failed. Last error: {}", last_error))
|
||
}
|
||
|
||
/// 简化的嵌入式Python设置(使用批处理脚本)
|
||
#[command]
|
||
pub async fn setup_embedded_python_simple(_app: AppHandle) -> Result<String, String> {
|
||
println!("Setting up embedded Python environment using batch script...");
|
||
|
||
// 查找批处理脚本
|
||
let possible_script_paths = vec![
|
||
std::env::current_dir().ok()
|
||
.map(|p| p.join("scripts").join("setup_embedded_python.bat")),
|
||
Some(std::path::PathBuf::from("scripts/setup_embedded_python.bat")),
|
||
Some(std::path::PathBuf::from("./scripts/setup_embedded_python.bat")),
|
||
];
|
||
|
||
let mut script_path = None;
|
||
for path in possible_script_paths.into_iter().flatten() {
|
||
println!("Checking batch script path: {:?}", path);
|
||
if path.exists() {
|
||
script_path = Some(path);
|
||
break;
|
||
}
|
||
}
|
||
|
||
let script_path = script_path.ok_or_else(|| {
|
||
let current_dir = std::env::current_dir().unwrap_or_default();
|
||
format!(
|
||
"Batch script not found. Current directory: {:?}. Please ensure scripts/setup_embedded_python.bat exists.",
|
||
current_dir
|
||
)
|
||
})?;
|
||
|
||
println!("Using batch script: {:?}", script_path);
|
||
|
||
// 在Windows上运行批处理脚本
|
||
#[cfg(target_os = "windows")]
|
||
{
|
||
let mut cmd = Command::new("cmd");
|
||
cmd.args(&["/C", &script_path.to_string_lossy()]);
|
||
cmd.current_dir(std::env::current_dir().unwrap_or_default());
|
||
|
||
match cmd.output() {
|
||
Ok(output) => {
|
||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
|
||
if output.status.success() {
|
||
Ok(format!("Embedded Python setup completed:\nOutput: {}\nErrors: {}", stdout, stderr))
|
||
} else {
|
||
Err(format!("Batch script failed:\nOutput: {}\nErrors: {}", stdout, stderr))
|
||
}
|
||
}
|
||
Err(e) => Err(format!("Failed to execute batch script: {}", e))
|
||
}
|
||
}
|
||
|
||
// 在非Windows系统上返回错误
|
||
#[cfg(not(target_os = "windows"))]
|
||
{
|
||
Err("Batch script setup is only available on Windows. Please run the Python script manually.".to_string())
|
||
}
|
||
}
|