Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions crates/uv-python/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ impl Display for StatusCodeError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Querying Python at `{}` failed with exit status {}",
"Querying Python at `{}` failed with {}",
self.path.display(),
self.code
)?;
Expand Down Expand Up @@ -903,6 +903,27 @@ pub enum InterpreterInfoError {
},
#[error("Only Pyodide is support for Emscripten Python")]
EmscriptenNotPyodide,
#[error("Python is missing PYTHONHOME. If you are using a managed Python interpreter, this is a known bug (https://github.com/astral-sh/python-build-standalone/issues/380). You can recreate the virtual environment with `{}`.", "uv venv".green())]
PythonHomeNotFound,
}

impl InterpreterInfoError {
/// Check whether the stderr of `python` matches a known pattern.
pub(crate) fn from_query_stderr(stderr: &str) -> Option<Self> {
// If the Python version is too old, we may not even be able to invoke the query script
if stderr.contains("Unknown option: -I") {
return Some(Self::UnsupportedPython);
}

// Until we fixed the PBS bug, inform the user that this is bug on our side and can be fixed
// with `uv venv`.
// https://github.com/astral-sh/python-build-standalone/issues/380
if stderr.contains("ModuleNotFoundError: No module named 'encodings'") {
return Some(Self::PythonHomeNotFound);
}

None
}
}

#[allow(clippy::struct_excessive_bools)]
Expand Down Expand Up @@ -979,10 +1000,9 @@ impl InterpreterInfo {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();

// If the Python version is too old, we may not even be able to invoke the query script
if stderr.contains("Unknown option: -I") {
if let Some(query_error) = InterpreterInfoError::from_query_stderr(&stderr) {
return Err(Error::QueryScript {
err: InterpreterInfoError::UnsupportedPython,
err: query_error,
path: interpreter.to_path_buf(),
});
}
Expand All @@ -999,20 +1019,19 @@ impl InterpreterInfo {
serde_json::from_slice(&output.stdout).map_err(|err| {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();

// If the Python version is too old, we may not even be able to invoke the query script
if stderr.contains("Unknown option: -I") {
Error::QueryScript {
err: InterpreterInfoError::UnsupportedPython,
path: interpreter.to_path_buf(),
}
} else {
Error::UnexpectedResponse(UnexpectedResponseError {
err,
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
stderr,
if let Some(query_error) = InterpreterInfoError::from_query_stderr(&stderr) {
return Error::QueryScript {
err: query_error,
path: interpreter.to_path_buf(),
})
};
}

Error::UnexpectedResponse(UnexpectedResponseError {
err,
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
stderr,
path: interpreter.to_path_buf(),
})
})?;

match result {
Expand Down
45 changes: 45 additions & 0 deletions crates/uv/tests/it/python_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3650,3 +3650,48 @@ fn python_install_build_version_pypy() {
error: No download found for request: pypy-3.10-[PLATFORM]
");
}

/// Show a fitting error message for
/// <https://github.com/astral-sh/python-build-standalone/issues/380>.
#[cfg(unix)]
#[test]
fn missing_python_home_error_message() -> anyhow::Result<()> {
let context = TestContext::new("3.12");

// Create a Python project so we can use `uv sync`
context.init().assert().success();

// Create a broken venv from a symlink.
let sys_executable = fs_err::canonicalize(context.venv.join("bin").join("python"))?;
fs_err::os::unix::fs::symlink(sys_executable, context.temp_dir.join("python-link"))?;
fs_err::remove_dir_all(context.venv.as_ref())?;
Command::new(context.temp_dir.join("python-link"))
.arg("-m")
.arg("venv")
.arg("--without-pip")
.arg(context.venv.as_ref())
.assert()
.success();

uv_snapshot!(context.filters(), context.pip_list().arg("-p").arg(context.venv.join("bin").join("python")), @r"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
error: Failed to inspect Python interpreter from provided path at `.venv/bin/python`
Caused by: Can't use Python at `[VENV]/bin/python`
Caused by: Python is missing PYTHONHOME. If you are using a managed Python interpreter, this is a known bug (https://github.com/astral-sh/python-build-standalone/issues/380). You can recreate the virtual environment with `uv venv`.
");

// By default, we skip broken interpreters
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ideal but it's consistent with the existing logic.

uv_snapshot!(context.filters(), context.pip_list(), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
");

Ok(())
}
Loading