Show a better hint for broken PBS venvs

For some reason, I'm encountering broken PBS venvs again (https://github.com/astral-sh/python-build-standalone/issues/380). Instead of showing the obscure error to the user, we point them to the PBS bug and ask them to run `uv venv`.

```
$ uv sync
error: Querying Python at `/tmp/uv/tests/.tmpwXZRTI/temp/.venv/bin/python3` failed with exit status exit status: 1

[stderr]
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Python path configuration:
  PYTHONHOME = (not set)
  PYTHONPATH = (not set)
  program name = '/tmp/uv/tests/.tmpwXZRTI/temp/.venv/bin/python3'
  isolated = 1
  environment = 0
  user site = 0
  safe_path = 1
  import site = 1
  is in build tree = 0
  stdlib dir = '/install/lib/python3.12'
  sys._base_executable = '/home/konsti/.local/share/uv/python/cpython-3.12.9-linux-x86_64-gnu/bin/python3.12'
  sys.base_prefix = '/install'
  sys.base_exec_prefix = '/install'
  sys.platlibdir = 'lib'
  sys.executable = '/tmp/uv/tests/.tmpwXZRTI/temp/.venv/bin/python3'
  sys.prefix = '/install'
  sys.exec_prefix = '/install'
  sys.path = [
    '/install/lib/python312.zip',
    '/install/lib/python3.12',
    '/install/lib/python3.12/lib-dynload',
  ]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'

Current thread 0x000078e48b835740 (most recent call first):
  <no Python frame>
```

```
$ uv-debug sync
  error: Can't use Python at `/tmp/uv/tests/.tmpwXZRTI/temp/.venv/bin/python3`
    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`.
```
This commit is contained in:
konstin 2025-10-23 17:39:16 +02:00
parent f88be7d154
commit a2b603f8fe
2 changed files with 80 additions and 15 deletions

View File

@ -918,6 +918,27 @@ pub enum InterpreterInfoError {
}, },
#[error("Only Pyodide is support for Emscripten Python")] #[error("Only Pyodide is support for Emscripten Python")]
EmscriptenNotPyodide, 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)] #[allow(clippy::struct_excessive_bools)]
@ -994,10 +1015,9 @@ impl InterpreterInfo {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); 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 let Some(query_error) = InterpreterInfoError::from_query_stderr(&stderr) {
if stderr.contains("Unknown option: -I") {
return Err(Error::QueryScript { return Err(Error::QueryScript {
err: InterpreterInfoError::UnsupportedPython, err: query_error,
path: interpreter.to_path_buf(), path: interpreter.to_path_buf(),
}); });
} }
@ -1014,20 +1034,19 @@ impl InterpreterInfo {
serde_json::from_slice(&output.stdout).map_err(|err| { serde_json::from_slice(&output.stdout).map_err(|err| {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); 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 let Some(query_error) = InterpreterInfoError::from_query_stderr(&stderr) {
if stderr.contains("Unknown option: -I") { return Error::QueryScript {
Error::QueryScript { err: query_error,
err: InterpreterInfoError::UnsupportedPython,
path: interpreter.to_path_buf(), path: interpreter.to_path_buf(),
} };
} else {
Error::UnexpectedResponse(UnexpectedResponseError {
err,
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
stderr,
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 { match result {

View File

@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::{env, path::Path, process::Command}; use std::{env, path::Path, process::Command};
use crate::common::{TestContext, uv_snapshot}; use crate::common::{TestContext, uv_snapshot};
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt; use assert_cmd::assert::OutputAssertExt;
use assert_fs::{ use assert_fs::{
assert::PathAssert, assert::PathAssert,
@ -3950,3 +3951,48 @@ fn python_install_upgrade_version_file() {
hint: The version request came from a `.python-version` file; change the patch version in the file to upgrade instead hint: The version request came from a `.python-version` file; change the patch version in the file to upgrade instead
"); ");
} }
/// 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() -> 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
uv_snapshot!(context.filters(), context.pip_list(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
Ok(())
}