diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index 2146d5548..2d9f0aaf3 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -49,6 +49,7 @@ struct State { pyproject_modified_by_user: bool, previous_version: Option, resources_dir: std::path::PathBuf, + venv_folder: std::path::PathBuf, } #[derive(Debug, Clone)] @@ -110,6 +111,7 @@ fn run() -> Result<()> { pyproject_modified_by_user: false, // calculated later previous_version: None, resources_dir, + venv_folder: uv_install_root.join(".venv"), }; // Check for uninstall request from Windows uninstaller @@ -122,7 +124,8 @@ fn run() -> Result<()> { // Create install directory create_dir_all(&state.uv_install_root)?; - let launcher_requested = state.launcher_trigger_file.exists(); + let launcher_requested = + state.launcher_trigger_file.exists() || !state.user_pyproject_path.exists(); // Calculate whether user has custom edits that need syncing let pyproject_time = file_timestamp_secs(&state.user_pyproject_path); @@ -152,7 +155,7 @@ fn run() -> Result<()> { check_versions(&mut state); - let first_run = !state.uv_install_root.join(".venv").exists(); + let first_run = !state.venv_folder.exists(); if first_run { handle_version_install_or_update(&state, MainMenuChoice::Latest)?; } else { @@ -160,7 +163,7 @@ fn run() -> Result<()> { } // Write marker file to indicate we've completed the sync process - write_sync_marker(&state.sync_complete_marker)?; + write_sync_marker(&state)?; #[cfg(target_os = "macos")] { @@ -186,13 +189,15 @@ fn run() -> Result<()> { Ok(()) } -fn extract_aqt_version( - uv_path: &std::path::Path, - uv_install_root: &std::path::Path, -) -> Option { - let output = Command::new(uv_path) - .current_dir(uv_install_root) - .env("VIRTUAL_ENV", uv_install_root.join(".venv")) +fn extract_aqt_version(state: &State) -> Option { + // Check if .venv exists first + if !state.venv_folder.exists() { + return None; + } + + let output = Command::new(&state.uv_path) + .current_dir(&state.uv_install_root) + .env("VIRTUAL_ENV", &state.venv_folder) .args(["pip", "show", "aqt"]) .output() .ok()?; @@ -217,7 +222,7 @@ fn check_versions(state: &mut State) { } // Determine current version by invoking uv pip show aqt - match extract_aqt_version(&state.uv_path, &state.uv_install_root) { + match extract_aqt_version(state) { Some(version) => { state.current_version = Some(version); } @@ -242,12 +247,12 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re update_pyproject_for_version(choice.clone(), state)?; // Extract current version before syncing (but don't write to file yet) - let previous_version_to_save = extract_aqt_version(&state.uv_path, &state.uv_install_root); + let previous_version_to_save = extract_aqt_version(state); // Remove sync marker before attempting sync let _ = remove_file(&state.sync_complete_marker); - println!("\x1B[1mUpdating Anki...\x1B[0m\n"); + println!("Updating Anki...\n"); let python_version_trimmed = if state.user_python_version_path.exists() { let python_version = read_file(&state.user_python_version_path)?; @@ -258,6 +263,11 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re None }; + let have_venv = state.venv_folder.exists(); + if cfg!(target_os = "macos") && !have_developer_tools() && !have_venv { + println!("If you see a pop-up about 'install_name_tool', you can cancel it, and ignore the warning below.\n"); + } + // Prepare to sync the venv let mut command = Command::new(&state.uv_path); command.current_dir(&state.uv_install_root); @@ -305,7 +315,7 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re Ok(_) => { // Sync succeeded if matches!(&choice, MainMenuChoice::Version(VersionKind::PyOxidizer(_))) { - inject_helper_addon(&state.uv_install_root)?; + inject_helper_addon()?; } // Now that sync succeeded, save the previous version @@ -384,12 +394,12 @@ fn main_menu_loop(state: &State) -> Result<()> { Ok(()) } -fn write_sync_marker(sync_complete_marker: &std::path::Path) -> Result<()> { +fn write_sync_marker(state: &State) -> Result<()> { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .context("Failed to get system time")? .as_secs(); - write_file(sync_complete_marker, timestamp.to_string())?; + write_file(&state.sync_complete_marker, timestamp.to_string())?; Ok(()) } @@ -483,8 +493,6 @@ fn get_main_menu_choice(state: &State) -> Result { } fn get_version_kind(state: &State) -> Result> { - println!("Please wait..."); - let releases = get_releases(state)?; let releases_str = releases .latest @@ -668,6 +676,7 @@ fn fetch_versions(state: &State) -> Result> { } fn get_releases(state: &State) -> Result { + println!("Checking for updates..."); let include_prereleases = state.prerelease_marker.exists(); let all_versions = fetch_versions(state)?; let all_versions = filter_and_normalize_versions(all_versions, include_prereleases); @@ -794,7 +803,7 @@ fn parse_version_kind(version: &str) -> Option { } } -fn inject_helper_addon(_uv_install_root: &std::path::Path) -> Result<()> { +fn inject_helper_addon() -> Result<()> { let addons21_path = get_anki_addons21_path()?; if !addons21_path.exists() { @@ -896,16 +905,24 @@ fn handle_uninstall(state: &State) -> Result { Ok(true) } +fn have_developer_tools() -> bool { + Command::new("xcode-select") + .args(["-p"]) + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + fn build_python_command(state: &State, args: &[String]) -> Result { let python_exe = if cfg!(target_os = "windows") { let show_console = std::env::var("ANKI_CONSOLE").is_ok(); if show_console { - state.uv_install_root.join(".venv/Scripts/python.exe") + state.venv_folder.join("Scripts/python.exe") } else { - state.uv_install_root.join(".venv/Scripts/pythonw.exe") + state.venv_folder.join("Scripts/pythonw.exe") } } else { - state.uv_install_root.join(".venv/bin/python") + state.venv_folder.join("bin/python") }; let mut cmd = Command::new(&python_exe);