mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 13:58:29 +00:00
Patch embedded install path for Python dylib on macOS during python install
(#10629)
## Summary Fixes #10598 ## Test Plan Looking for input here @zanieb. How/where would you include tests for this? More broadly: do we want a failure to perform the rename to be a hard error? Or should it start out as a warning? --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
04fc36f066
commit
1af02ce8f2
6 changed files with 141 additions and 2 deletions
|
@ -165,6 +165,9 @@ impl PythonInstallation {
|
||||||
installed.ensure_externally_managed()?;
|
installed.ensure_externally_managed()?;
|
||||||
installed.ensure_sysconfig_patched()?;
|
installed.ensure_sysconfig_patched()?;
|
||||||
installed.ensure_canonical_executables()?;
|
installed.ensure_canonical_executables()?;
|
||||||
|
if let Err(e) = installed.ensure_dylib_patched() {
|
||||||
|
e.warn_user(&installed);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source: PythonSource::Managed,
|
source: PythonSource::Managed,
|
||||||
|
|
|
@ -30,6 +30,7 @@ mod implementation;
|
||||||
mod installation;
|
mod installation;
|
||||||
mod interpreter;
|
mod interpreter;
|
||||||
mod libc;
|
mod libc;
|
||||||
|
pub mod macos_dylib;
|
||||||
pub mod managed;
|
pub mod managed;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod microsoft_store;
|
mod microsoft_store;
|
||||||
|
|
63
crates/uv-python/src/macos_dylib.rs
Normal file
63
crates/uv-python/src/macos_dylib.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::{io::ErrorKind, path::PathBuf};
|
||||||
|
|
||||||
|
use uv_fs::Simplified as _;
|
||||||
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
|
use crate::managed::ManagedPythonInstallation;
|
||||||
|
|
||||||
|
pub fn patch_dylib_install_name(dylib: PathBuf) -> Result<(), Error> {
|
||||||
|
let output = match std::process::Command::new("install_name_tool")
|
||||||
|
.arg("-id")
|
||||||
|
.arg(&dylib)
|
||||||
|
.arg(&dylib)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => {
|
||||||
|
let e = if e.kind() == ErrorKind::NotFound {
|
||||||
|
Error::MissingInstallNameTool
|
||||||
|
} else {
|
||||||
|
e.into()
|
||||||
|
};
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||||
|
return Err(Error::RenameError { dylib, stderr });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("`install_name_tool` is not available on this system.
|
||||||
|
This utility is part of macOS Developer Tools. Please ensure that the Xcode Command Line Tools are installed by running:
|
||||||
|
|
||||||
|
xcode-select --install
|
||||||
|
|
||||||
|
For more information, see: https://developer.apple.com/xcode/")]
|
||||||
|
MissingInstallNameTool,
|
||||||
|
#[error("Failed to update the install name of the Python dynamic library located at `{}`", dylib.user_display())]
|
||||||
|
RenameError { dylib: PathBuf, stderr: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Emit a user-friendly warning about the patching failure.
|
||||||
|
pub fn warn_user(&self, installation: &ManagedPythonInstallation) {
|
||||||
|
let error = if tracing::enabled!(tracing::Level::DEBUG) {
|
||||||
|
format!("\nUnderlying error: {self}")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
warn_user!(
|
||||||
|
"Failed to patch the install name of the dynamic library for {}. This may cause issues when building Python native extensions.{}",
|
||||||
|
installation.executable().simplified_display(),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,8 @@ use crate::libc::LibcDetectionError;
|
||||||
use crate::platform::Error as PlatformError;
|
use crate::platform::Error as PlatformError;
|
||||||
use crate::platform::{Arch, Libc, Os};
|
use crate::platform::{Arch, Libc, Os};
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
use crate::{sysconfig, PythonRequest, PythonVariant};
|
use crate::{macos_dylib, sysconfig, PythonRequest, PythonVariant};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -88,6 +89,8 @@ pub enum Error {
|
||||||
NameParseError(#[from] installation::PythonInstallationKeyError),
|
NameParseError(#[from] installation::PythonInstallationKeyError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
LibcDetection(#[from] LibcDetectionError),
|
LibcDetection(#[from] LibcDetectionError),
|
||||||
|
#[error(transparent)]
|
||||||
|
MacOsDylib(#[from] macos_dylib::Error),
|
||||||
}
|
}
|
||||||
/// A collection of uv-managed Python installations installed on the current system.
|
/// A collection of uv-managed Python installations installed on the current system.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
@ -508,6 +511,28 @@ impl ManagedPythonInstallation {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On macOS, ensure that the `install_name` for the Python dylib is set
|
||||||
|
/// correctly, rather than pointing at `/install/lib/libpython{version}.dylib`.
|
||||||
|
/// This is necessary to ensure that native extensions written in Rust
|
||||||
|
/// link to the correct location for the Python library.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/astral-sh/uv/issues/10598> for more information.
|
||||||
|
pub fn ensure_dylib_patched(&self) -> Result<(), macos_dylib::Error> {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
if *self.implementation() == ImplementationName::CPython {
|
||||||
|
let dylib_path = self.python_dir().join("lib").join(format!(
|
||||||
|
"{}python{}{}{}",
|
||||||
|
std::env::consts::DLL_PREFIX,
|
||||||
|
self.key.version().python_version(),
|
||||||
|
self.key.variant().suffix(),
|
||||||
|
std::env::consts::DLL_SUFFIX
|
||||||
|
));
|
||||||
|
macos_dylib::patch_dylib_install_name(dylib_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a link to the managed Python executable.
|
/// Create a link to the managed Python executable.
|
||||||
///
|
///
|
||||||
/// If the file already exists at the target path, an error will be returned.
|
/// If the file already exists at the target path, an error will be returned.
|
||||||
|
@ -603,7 +628,7 @@ impl ManagedPythonInstallation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a platform portion of a key from the environment.
|
/// Generate a platform portion of a key from the environment.
|
||||||
fn platform_key_from_env() -> Result<String, Error> {
|
pub fn platform_key_from_env() -> Result<String, Error> {
|
||||||
let os = Os::from_env();
|
let os = Os::from_env();
|
||||||
let arch = Arch::from_env();
|
let arch = Arch::from_env();
|
||||||
let libc = Libc::from_env()?;
|
let libc = Libc::from_env()?;
|
||||||
|
|
|
@ -312,6 +312,9 @@ pub(crate) async fn install(
|
||||||
installation.ensure_externally_managed()?;
|
installation.ensure_externally_managed()?;
|
||||||
installation.ensure_sysconfig_patched()?;
|
installation.ensure_sysconfig_patched()?;
|
||||||
installation.ensure_canonical_executables()?;
|
installation.ensure_canonical_executables()?;
|
||||||
|
if let Err(e) = installation.ensure_dylib_patched() {
|
||||||
|
e.warn_user(installation);
|
||||||
|
}
|
||||||
|
|
||||||
if preview.is_disabled() {
|
if preview.is_disabled() {
|
||||||
debug!("Skipping installation of Python executables, use `--preview` to enable.");
|
debug!("Skipping installation of Python executables, use `--preview` to enable.");
|
||||||
|
|
|
@ -876,3 +876,47 @@ fn python_install_preview_broken_link() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[test]
|
||||||
|
fn python_dylib_install_name_is_patched_on_install() {
|
||||||
|
use assert_cmd::assert::OutputAssertExt;
|
||||||
|
use uv_python::managed::platform_key_from_env;
|
||||||
|
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_keys();
|
||||||
|
|
||||||
|
// Install the latest version
|
||||||
|
context
|
||||||
|
.python_install()
|
||||||
|
.arg("--preview")
|
||||||
|
.arg("3.13.1")
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
|
let dylib = context
|
||||||
|
.temp_dir
|
||||||
|
.child("managed")
|
||||||
|
.child(format!(
|
||||||
|
"cpython-3.13.1-{}",
|
||||||
|
platform_key_from_env().unwrap()
|
||||||
|
))
|
||||||
|
.child("lib")
|
||||||
|
.child(format!(
|
||||||
|
"{}python3.13{}",
|
||||||
|
std::env::consts::DLL_PREFIX,
|
||||||
|
std::env::consts::DLL_SUFFIX
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new("otool");
|
||||||
|
cmd.arg("-D").arg(dylib.as_ref());
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), cmd, @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[TEMP_DIR]/managed/cpython-3.13.1-[PLATFORM]/lib/libpython3.13.dylib:
|
||||||
|
[TEMP_DIR]/managed/cpython-3.13.1-[PLATFORM]/lib/libpython3.13.dylib
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue