From d42345e6f185b1962967cc4dfe585dea5b7ddd97 Mon Sep 17 00:00:00 2001 From: NhanHo Date: Thu, 26 Mar 2026 10:20:16 +0700 Subject: [PATCH] fix: discover Python from pyenv, conda, and user shell PATH GUI apps on macOS don't inherit the user's shell PATH, so Python installed via pyenv or conda was invisible to the MLX setup and local pipeline. Add shared helpers that check common install locations and query the user's login shell for their real PATH. --- package-lock.json | 4 +- scripts/setup_mlx.py | 4 + src-tauri/Cargo.lock | 63 ++++++++------ src-tauri/src/commands/local_pipeline.rs | 101 +++++++++++++++++++---- 4 files changed, 130 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58ab406..6a6da4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "personal-translator", + "name": "my-translator", "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "personal-translator", + "name": "my-translator", "version": "0.4.0", "dependencies": { "@tauri-apps/plugin-process": "^2.3.1", diff --git a/scripts/setup_mlx.py b/scripts/setup_mlx.py index 76dce9d..03b8ec4 100644 --- a/scripts/setup_mlx.py +++ b/scripts/setup_mlx.py @@ -56,9 +56,13 @@ def is_setup_complete(env_dir): def check_system_python(): """Find a suitable Python 3.10+ for creating venv.""" + home = os.path.expanduser("~") candidates = [ + os.path.join(home, ".pyenv", "shims", "python3"), "/opt/homebrew/bin/python3", "/usr/local/bin/python3", + os.path.join(home, "miniconda3", "bin", "python3"), + os.path.join(home, "anaconda3", "bin", "python3"), shutil.which("python3"), ] for path in candidates: diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8f0e2e3..3a1147f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2406,6 +2406,33 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "my-translator" +version = "0.4.2" +dependencies = [ + "base64 0.22.1", + "chrono", + "cpal", + "dirs", + "futures-util", + "hex", + "http", + "rand 0.8.5", + "reqwest 0.12.28", + "screencapturekit", + "serde", + "serde_json", + "sha2", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-process", + "tauri-plugin-updater", + "tokio-tungstenite", + "uuid", + "windows 0.58.0", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -2863,32 +2890,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "personal-translator" -version = "0.4.2" -dependencies = [ - "base64 0.22.1", - "chrono", - "cpal", - "dirs", - "futures-util", - "hex", - "http", - "rand 0.8.5", - "reqwest 0.12.28", - "screencapturekit", - "serde", - "serde_json", - "sha2", - "tauri", - "tauri-build", - "tauri-plugin-opener", - "tauri-plugin-updater", - "tokio-tungstenite", - "uuid", - "windows 0.58.0", -] - [[package]] name = "phf" version = "0.8.0" @@ -4578,6 +4579,16 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-updater" version = "2.10.0" diff --git a/src-tauri/src/commands/local_pipeline.rs b/src-tauri/src/commands/local_pipeline.rs index b63f2fd..4785590 100644 --- a/src-tauri/src/commands/local_pipeline.rs +++ b/src-tauri/src/commands/local_pipeline.rs @@ -8,6 +8,80 @@ pub struct LocalPipelineState { pub process: Mutex>, } +/// Build a PATH that includes common Python install locations and the user's shell PATH. +/// GUI apps on macOS don't inherit the user's shell PATH, so we must reconstruct it. +fn build_rich_path() -> String { + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/root")); + let mut dirs: Vec = vec![ + // Homebrew (Apple Silicon + Intel) + "/opt/homebrew/bin".into(), + "/usr/local/bin".into(), + // pyenv + format!("{}/.pyenv/shims", home), + format!("{}/.pyenv/bin", home), + // Typical conda locations + format!("{}/miniconda3/bin", home), + format!("{}/anaconda3/bin", home), + // System + "/usr/bin".into(), + "/bin".into(), + ]; + + // Also try to get the real user PATH from a login shell (best-effort) + if let Ok(output) = Command::new("/bin/zsh") + .args(["-l", "-c", "echo $PATH"]) + .output() + { + if output.status.success() { + let shell_path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + for entry in shell_path.split(':') { + let s = entry.to_string(); + if !dirs.contains(&s) { + dirs.push(s); + } + } + } + } + + dirs.join(":") +} + +/// Find a working Python 3.10+ binary, checking common locations. +fn find_python3() -> String { + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/root")); + let candidates = [ + format!("{}/.pyenv/shims/python3", home), + "/opt/homebrew/bin/python3".into(), + "/usr/local/bin/python3".into(), + format!("{}/miniconda3/bin/python3", home), + format!("{}/anaconda3/bin/python3", home), + ]; + + for path in &candidates { + let p = std::path::Path::new(path); + if p.exists() { + // Verify it's actually 3.10+ + if let Ok(output) = Command::new(path).arg("--version").output() { + let ver = String::from_utf8_lossy(&output.stdout); + if let Some(version) = ver.strip_prefix("Python ") { + let parts: Vec<&str> = version.trim().split('.').collect(); + if let (Ok(major), Ok(minor)) = ( + parts.first().unwrap_or(&"0").parse::(), + parts.get(1).unwrap_or(&"0").parse::(), + ) { + if major >= 3 && minor >= 10 { + return path.clone(); + } + } + } + } + } + } + + // Fallback — hope it's on PATH + "python3".into() +} + fn log_to_file(msg: &str) { use std::fs::OpenOptions; let _ = OpenOptions::new() @@ -80,20 +154,19 @@ pub fn start_local_pipeline( let _ = channel.send(format!(r#"{{"type":"status","message":"Starting Python pipeline..."}}"#)); // Use venv python if MLX setup is complete, otherwise fall back to system python - let home = std::env::var("HOME").unwrap_or_else(|_| "/Users/phucnt".to_string()); + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/root")); let venv_python = format!("{}/Library/Application Support/My Translator/mlx-env/bin/python3", home); let python = if std::path::Path::new(&venv_python).exists() { log_to_file(&format!("Using venv python: {}", venv_python)); - venv_python.as_str().to_string() - } else if std::path::Path::new("/opt/homebrew/bin/python3").exists() { - log_to_file("Using homebrew python"); - "/opt/homebrew/bin/python3".to_string() + venv_python.clone() } else { - "python3".to_string() + let found = find_python3(); + log_to_file(&format!("Using system python: {}", found)); + found }; - let path_env = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"; + let path_env = build_rich_path(); let mut child = Command::new(&python) .arg(&script_path) @@ -222,7 +295,7 @@ fn stop_local_pipeline_inner(state: &LocalPipelineState) { /// Check if MLX setup is complete #[tauri::command] pub fn check_mlx_setup() -> Result { - let home = std::env::var("HOME").unwrap_or_else(|_| "/Users/phucnt".to_string()); + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/root")); let marker = format!("{}/Library/Application Support/My Translator/mlx-env/.setup_complete", home); let venv_python = format!("{}/Library/Application Support/My Translator/mlx-env/bin/python3", home); @@ -261,14 +334,14 @@ pub fn run_mlx_setup( }; // Use system python to run setup (which creates the venv) - let python = if std::path::Path::new("/opt/homebrew/bin/python3").exists() { - "/opt/homebrew/bin/python3" - } else { - "python3" - }; + let python = find_python3(); + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/root")); + let path_env = build_rich_path(); - let mut child = Command::new(python) + let mut child = Command::new(&python) .arg(&script_path) + .env("PATH", &path_env) + .env("HOME", &home) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()