From 4f31b44eac746f030a8569482554898aa01afc62 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 20 Jan 2025 11:29:29 -0600 Subject: [PATCH] Improve log when distutils is missing (#10713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/astral-sh/uv/issues/4204 for motivation This doesn't really reach the user experience I'd expect — i.e., we end up saying a virtual environment "does not exist" which is a little silly. However, I think improving the error messaging on interpreter queries in general should be solved separately. I did one small "general" change in https://github.com/astral-sh/uv/pull/10713/commits/89e11d022222a999a4ba8eb73397ea314a636811 — otherwise we don't show the message at all. --------- Co-authored-by: konsti --- .github/workflows/ci.yml | 45 ++++++++++++++++++- .../uv-python/python/get_interpreter_info.py | 43 +++++++++++++----- crates/uv-python/src/discovery.rs | 10 ++++- crates/uv-python/src/interpreter.rs | 5 +++ 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3af157915..c9d9b9a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -680,13 +680,56 @@ jobs: echo "$CONDA_PREFIX" ./uv pip install anyio + integration-test-deadsnakes-39-linux: + timeout-minutes: 5 + needs: build-binary-linux + name: "integration test | deadsnakes python3.9 on ubuntu" + runs-on: ubuntu-latest + steps: + - name: "Install python3.9" + run: | + sudo add-apt-repository ppa:deadsnakes + sudo apt-get update + sudo apt-get install python3.9 + + - name: "Download binary" + uses: actions/download-artifact@v4 + with: + name: uv-linux-${{ github.sha }} + + - name: "Prepare binary" + run: chmod +x ./uv + + - name: "Create a virtual environment" + run: | + ./uv venv -p 3.9 --python-preference only-system + + - name: "Check version" + run: | + .venv/bin/python --version + + - name: "Check install missing distutils" + run: | + ./uv pip install -v anyio 2>&1 | tee log.txt || true + # We should report that distutils is missing + grep 'Python installation is missing `distutils`' log.txt + + - name: "Install distutils" + run: | + sudo apt-get install python3.9-distutils + + - name: "Check install" + run: | + ./uv pip install -v anyio + # Now the install should succeed + integration-test-free-threaded-linux: timeout-minutes: 5 needs: build-binary-linux name: "integration test | free-threaded on linux" runs-on: ubuntu-latest steps: - - name: "install python3.13-nogil" + - name: "Install python3.13-nogil" run: | sudo add-apt-repository ppa:deadsnakes sudo apt-get update diff --git a/crates/uv-python/python/get_interpreter_info.py b/crates/uv-python/python/get_interpreter_info.py index 939ad35af..86db1d4fe 100644 --- a/crates/uv-python/python/get_interpreter_info.py +++ b/crates/uv-python/python/get_interpreter_info.py @@ -224,7 +224,7 @@ def get_virtualenv(): } -def get_scheme(): +def get_scheme(use_sysconfig_scheme: bool): """Return the Scheme for the current interpreter. The paths returned should be absolute. @@ -401,15 +401,7 @@ def get_scheme(): "data": scheme["data"], } - # By default, pip uses sysconfig on Python 3.10+. - # But Python distributors can override this decision by setting: - # sysconfig._PIP_USE_SYSCONFIG = True / False - # Rationale in https://github.com/pypa/pip/issues/10647 - use_sysconfig = bool( - getattr(sysconfig, "_PIP_USE_SYSCONFIG", sys.version_info >= (3, 10)) - ) - - if use_sysconfig: + if use_sysconfig_scheme: return get_sysconfig_scheme() else: return get_distutils_scheme() @@ -571,6 +563,35 @@ def main() -> None: elif os_and_arch["os"]["name"] == "musllinux": manylinux_compatible = True + + # By default, pip uses sysconfig on Python 3.10+. + # But Python distributors can override this decision by setting: + # sysconfig._PIP_USE_SYSCONFIG = True / False + # Rationale in https://github.com/pypa/pip/issues/10647 + use_sysconfig_scheme = bool( + getattr(sysconfig, "_PIP_USE_SYSCONFIG", sys.version_info >= (3, 10)) + ) + + # If we're not using sysconfig, make sure distutils is available. + if not use_sysconfig_scheme: + try: + import distutils.dist + except ImportError: + # We require distutils, but it's not installed; this is fairly + # common in, e.g., deadsnakes where distutils is packaged + # separately from Python. + print( + json.dumps( + { + "result": "error", + "kind": "missing_required_distutils", + "python_major": sys.version_info[0], + "python_minor": sys.version_info[1], + } + ) + ) + sys.exit(0) + interpreter_info = { "result": "success", "markers": markers, @@ -585,7 +606,7 @@ def main() -> None: # "/install" as the prefix. With `sysconfig` patching, we rewrite the prefix to match the actual installation # location. So in newer versions, we also write a dedicated flag to indicate standalone builds. "standalone": sysconfig.get_config_var("prefix") == "/install" or bool(sysconfig.get_config_var("PYTHON_BUILD_STANDALONE")), - "scheme": get_scheme(), + "scheme": get_scheme(use_sysconfig_scheme), "virtualenv": get_virtualenv(), "platform": os_and_arch, "manylinux_compatible": manylinux_compatible, diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index d35e3b0df..4d5088f7e 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -720,8 +720,7 @@ impl Error { InterpreterError::Encode(_) | InterpreterError::Io(_) | InterpreterError::SpawnFailed { .. } => true, - InterpreterError::QueryScript { path, .. } - | InterpreterError::UnexpectedResponse { path, .. } + InterpreterError::UnexpectedResponse { path, .. } | InterpreterError::StatusCode { path, .. } => { debug!( "Skipping bad interpreter at {} from {source}: {err}", @@ -729,6 +728,13 @@ impl Error { ); false } + InterpreterError::QueryScript { path, err } => { + debug!( + "Skipping bad interpreter at {} from {source}: {err}", + path.display() + ); + false + } InterpreterError::NotFound(path) => { // If the interpreter is from an active, valid virtual environment, we should // fail because it's broken diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 13a12be57..ec07c2450 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -604,6 +604,11 @@ pub enum InterpreterInfoError { UnsupportedPythonVersion { python_version: String }, #[error("Python executable does not support `-I` flag. Please use Python 3.8 or newer.")] UnsupportedPython, + #[error("Python installation is missing `distutils`, which is required for packaging on older Python versions. Your system may package it separately, e.g., as `python{python_major}-distutils` or `python{python_major}.{python_minor}-distutils`.")] + MissingRequiredDistutils { + python_major: usize, + python_minor: usize, + }, } #[derive(Debug, Deserialize, Serialize, Clone)]