diff --git a/crates/uv-python/src/macos_dylib.rs b/crates/uv-python/src/macos_dylib.rs index a77284da0..cf8f9c9f4 100644 --- a/crates/uv-python/src/macos_dylib.rs +++ b/crates/uv-python/src/macos_dylib.rs @@ -1,15 +1,28 @@ use std::{io::ErrorKind, path::PathBuf}; +use tempfile::tempdir_in; use uv_fs::Simplified as _; use uv_warnings::warn_user; use crate::managed::ManagedPythonInstallation; pub fn patch_dylib_install_name(dylib: PathBuf) -> Result<(), Error> { + // If `install_name_tool` fails, it seems to be capable of rendering the dylib unusable. + // So we'll create a copy, patch that, then, on success, replace the original + let tmp = tempdir_in( + dylib + .parent() + .expect("dylib should have a parent directory"), + )?; + let tmp_dylib = tmp + .path() + .join(dylib.file_name().expect("dylib should have a file name")); + fs_err::copy(&dylib, &tmp_dylib)?; + let output = match std::process::Command::new("install_name_tool") .arg("-id") .arg(&dylib) - .arg(&dylib) + .arg(&tmp_dylib) .output() { Ok(output) => output, @@ -28,6 +41,8 @@ pub fn patch_dylib_install_name(dylib: PathBuf) -> Result<(), Error> { return Err(Error::RenameError { dylib, stderr }); } + fs_err::rename(tmp_dylib, dylib)?; + Ok(()) } diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 41b046026..e5c97e2f5 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -75,23 +75,23 @@ fn python_install() { "###); // Should be a no-op when already installed - uv_snapshot!(context.filters(), context.python_install(), @r###" + uv_snapshot!(context.filters(), context.python_install(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Python is already installed. Use `uv python install ` to install another version. - "###); + "); // Similarly, when a requested version is already installed - uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - "###); + "); // You can opt-in to a reinstall uv_snapshot!(context.filters(), context.python_install().arg("3.13").arg("--reinstall"), @r" @@ -250,14 +250,14 @@ fn python_install_automatic() { // Otherwise, we should fetch the latest Python version uv_snapshot!(context.filters(), context.run() .env_remove("VIRTUAL_ENV") - .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###" + .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r" success: true exit_code: 0 ----- stdout ----- (3, 13) ----- stderr ----- - "###); + "); // Subsequently, we can use the interpreter even with downloads disabled uv_snapshot!(context.filters(), context.run() @@ -276,14 +276,14 @@ fn python_install_automatic() { uv_snapshot!(context.filters(), context.run() .env_remove("VIRTUAL_ENV") .arg("-p").arg("3.12") - .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###" + .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r" success: true exit_code: 0 ----- stdout ----- (3, 12) ----- stderr ----- - "###); + "); // But some requests cannot be mapped to a download uv_snapshot!(context.filters(), context.run() @@ -322,14 +322,14 @@ fn python_install_automatic() { // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin` .env("UV_TEST_PYTHON_PATH", context.bin_dir.as_os_str()) .arg("-p").arg("3.11") - .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r###" + .arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @r" success: true exit_code: 0 ----- stdout ----- (3, 11) ----- stderr ----- - "###); + "); } } @@ -358,14 +358,13 @@ fn regression_cpython() { uv_snapshot!(context.filters(), context.run() .env_remove("VIRTUAL_ENV") .arg("-p").arg("3.12") - .arg("mre.py"), @r###" + .arg("mre.py"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - - "###); + "); } #[test] @@ -630,14 +629,14 @@ fn python_install_preview() { "###); // Should be a no-op when already installed - uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Python is already installed. Use `uv python install ` to install another version. - "###); + "); // You can opt-in to a reinstall uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--reinstall"), @r" @@ -931,7 +930,7 @@ fn python_install_preview_upgrade() { } // Installing 3.12.4 should not replace the executable, but also shouldn't fail - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4"), @r" success: true exit_code: 0 ----- stdout ----- @@ -939,7 +938,7 @@ fn python_install_preview_upgrade() { ----- stderr ----- Installed Python 3.12.4 in [TIME] + cpython-3.12.4-[PLATFORM] - "###); + "); if cfg!(unix) { insta::with_settings!({ @@ -960,7 +959,7 @@ fn python_install_preview_upgrade() { } // Using `--reinstall` is not sufficient to replace it either - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4").arg("--reinstall"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4").arg("--reinstall"), @r" success: true exit_code: 0 ----- stdout ----- @@ -968,7 +967,7 @@ fn python_install_preview_upgrade() { ----- stderr ----- Installed Python 3.12.4 in [TIME] ~ cpython-3.12.4-[PLATFORM] - "###); + "); if cfg!(unix) { insta::with_settings!({ @@ -989,7 +988,7 @@ fn python_install_preview_upgrade() { } // But `--force` is - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4").arg("--force"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.4").arg("--force"), @r" success: true exit_code: 0 ----- stdout ----- @@ -997,7 +996,7 @@ fn python_install_preview_upgrade() { ----- stderr ----- Installed Python 3.12.4 in [TIME] + cpython-3.12.4-[PLATFORM] (python3.12) - "###); + "); if cfg!(unix) { insta::with_settings!({ @@ -1018,7 +1017,7 @@ fn python_install_preview_upgrade() { } // But installing 3.12.6 should upgrade automatically - uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.6"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.12.6"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1026,7 +1025,7 @@ fn python_install_preview_upgrade() { ----- stderr ----- Installed Python 3.12.6 in [TIME] + cpython-3.12.6-[PLATFORM] (python3.12) - "###); + "); if cfg!(unix) { insta::with_settings!({ @@ -2029,7 +2028,7 @@ fn python_install_patch_dylib() { let mut cmd = std::process::Command::new("otool"); cmd.arg("-D").arg(dylib.as_ref()); - uv_snapshot!(context.filters(), cmd, @r###" + uv_snapshot!(context.filters(), cmd, @r" success: true exit_code: 0 ----- stdout ----- @@ -2037,7 +2036,7 @@ fn python_install_patch_dylib() { [TEMP_DIR]/managed/cpython-3.13.1-[PLATFORM]/lib/libpython3.13.dylib ----- stderr ----- - "###); + "); } #[test] @@ -2172,14 +2171,14 @@ fn python_install_cached() { // Should be a no-op when already installed uv_snapshot!(context.filters(), context .python_install() - .env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r###" + .env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Python is already installed. Use `uv python install ` to install another version. - "###); + "); uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r" success: true