diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 0da3096f9..8c4dbc362 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1483,7 +1483,7 @@ mod tests { "root": "[ROOT]/albatross-root-workspace/packages/bird-feeder", "project": { "name": "bird-feeder", - "requires-python": ">=3.12", + "requires-python": ">=3.8", "optional-dependencies": null }, "pyproject_toml": "[PYPROJECT_TOML]" diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 63df358af..b595d8c89 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1,11 +1,12 @@ use std::fmt::Write; +use std::path::PathBuf; use itertools::Itertools; use owo_colors::OwoColorize; use tracing::debug; use distribution_types::{Resolution, UnresolvedRequirementSpecification}; -use pep440_rs::Version; +use pep440_rs::{Version, VersionSpecifiers}; use pypi_types::Requirement; use uv_auth::store_credentials_from_url; use uv_cache::Cache; @@ -17,6 +18,7 @@ use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_fs::Simplified; use uv_installer::{SatisfiesResult, SitePackages}; +use uv_normalize::PackageName; use uv_python::{ request_from_version_file, EnvironmentPreference, Interpreter, PythonEnvironment, PythonFetch, PythonInstallation, PythonPreference, PythonRequest, VersionRequest, @@ -60,6 +62,15 @@ pub(crate) enum ProjectError { #[error("The requested Python interpreter ({0}) is incompatible with the project Python requirement: `{1}`")] RequestedPythonIncompatibility(Version, RequiresPython), + #[error("The requested Python interpreter ({0}) is incompatible with the project Python requirement: `{1}`. However, a workspace member (`{member}`) supports Python {3}. To install the workspace member on its own, navigate to `{path}`, then run `{venv}` followed by `{install}`.", member = _2.cyan(), venv = format!("uv venv --python {_0}").green(), install = "uv pip install -e .".green(), path = _4.user_display().cyan() )] + RequestedMemberPythonIncompatibility( + Version, + RequiresPython, + PackageName, + VersionSpecifiers, + PathBuf, + ), + #[error(transparent)] Python(#[from] uv_python::Error), @@ -239,6 +250,29 @@ impl FoundInterpreter { if let Some(requires_python) = requires_python.as_ref() { if !requires_python.contains(interpreter.python_version()) { + // If the Python version is compatible with one of the workspace _members_, raise + // a dedicated error. For example, if the workspace root requires Python >=3.12, but + // a library in the workspace is compatible with Python >=3.8, the user may attempt + // to sync on Python 3.8. This will fail, but we should provide a more helpful error + // message. + for (name, member) in workspace.packages() { + let Some(project) = member.pyproject_toml().project.as_ref() else { + continue; + }; + let Some(specifiers) = project.requires_python.as_ref() else { + continue; + }; + if specifiers.contains(interpreter.python_version()) { + return Err(ProjectError::RequestedMemberPythonIncompatibility( + interpreter.python_version().clone(), + requires_python.clone(), + name.clone(), + specifiers.clone(), + member.root().clone(), + )); + } + } + return Err(ProjectError::RequestedPythonIncompatibility( interpreter.python_version().clone(), requires_python.clone(), diff --git a/crates/uv/tests/sync.rs b/crates/uv/tests/sync.rs index eee6f7efc..45c8478a9 100644 --- a/crates/uv/tests/sync.rs +++ b/crates/uv/tests/sync.rs @@ -355,7 +355,7 @@ fn mixed_requires_python() -> Result<()> { ----- stderr ----- warning: `uv sync` is experimental and may change without warning Using Python 3.8.[X] interpreter at: [PYTHON-3.8] - error: The requested Python interpreter (3.8.[X]) is incompatible with the project Python requirement: `>=3.12` + error: The requested Python interpreter (3.8.[X]) is incompatible with the project Python requirement: `>=3.12`. However, a workspace member (`bird-feeder`) supports Python >=3.8. To install the workspace member on its own, navigate to `packages/bird-feeder`, then run `uv venv --python 3.8.[X]` followed by `uv pip install -e .`. "###); Ok(()) diff --git a/scripts/workspaces/albatross-root-workspace/packages/bird-feeder/pyproject.toml b/scripts/workspaces/albatross-root-workspace/packages/bird-feeder/pyproject.toml index 0d42072be..7f6f7a2ff 100644 --- a/scripts/workspaces/albatross-root-workspace/packages/bird-feeder/pyproject.toml +++ b/scripts/workspaces/albatross-root-workspace/packages/bird-feeder/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "bird-feeder" version = "1.0.0" -requires-python = ">=3.12" +requires-python = ">=3.8" dependencies = ["anyio>=4.3.0,<5", "seeds"] [tool.uv.sources]