Do not replace the Python dylib on macOS if `install_name_tool` fails

This commit is contained in:
Zanie Blue 2025-07-24 06:43:22 -05:00
parent 1ddfcee9e9
commit 6f50cd842f
2 changed files with 42 additions and 28 deletions

View File

@ -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(())
}

View File

@ -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 <request>` 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 <request>` 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 <request>` to install another version.
"###);
");
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r"
success: true