mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Respect workspace-wide requires-python
in interpreter selection (#4298)
## Summary Closes https://github.com/astral-sh/uv/issues/4296. ## Test Plan Ran `cargo run lock --verbose` from `scripts/workspaces/albatross-virtual-workspace`: ``` DEBUG uv 0.2.11 (ef3bc1612 2024-06-12) warning: `uv lock` is experimental and may change without warning. DEBUG Found workspace root: `/Users/crmarsh/workspace/puffin/scripts/workspaces/albatross-virtual-workspace` DEBUG Adding discovered workspace member: /Users/crmarsh/workspace/puffin/scripts/workspaces/albatross-virtual-workspace/packages/albatross DEBUG Adding discovered workspace member: /Users/crmarsh/workspace/puffin/scripts/workspaces/albatross-virtual-workspace/packages/bird-feeder DEBUG Adding discovered workspace member: /Users/crmarsh/workspace/puffin/scripts/workspaces/albatross-virtual-workspace/packages/seeds DEBUG Searching for Python >=3.12 in search path or managed toolchains DEBUG Searching for managed toolchains at `/Users/crmarsh/Library/Application Support/uv/toolchains` DEBUG Found managed toolchain `cpython-3.12.3-macos-aarch64-none` DEBUG Found CPython 3.12.3 at `/Users/crmarsh/Library/Application Support/uv/toolchains/cpython-3.12.3-macos-aarch64-none/install/bin/python3` (managed toolchains) Using Python 3.12.3 interpreter at: /Users/crmarsh/Library/Application Support/uv/toolchains/cpython-3.12.3-macos-aarch64-none/install/bin/python3 ```
This commit is contained in:
parent
b43de79275
commit
5d1305aa6b
2 changed files with 39 additions and 29 deletions
|
@ -20,7 +20,7 @@ use uv_toolchain::Interpreter;
|
|||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::project::ProjectError;
|
||||
use crate::commands::project::{find_requires_python, ProjectError};
|
||||
use crate::commands::{pip, project, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
@ -106,17 +106,7 @@ pub(super) async fn do_lock(
|
|||
|
||||
// Determine the supported Python range. If no range is defined, and warn and default to the
|
||||
// current minor version.
|
||||
//
|
||||
// For a workspace, we compute the union of all workspace requires-python values, ensuring we
|
||||
// keep track of `None` vs. a full range.
|
||||
let requires_python =
|
||||
RequiresPython::union(workspace.packages().values().filter_map(|member| {
|
||||
member
|
||||
.pyproject_toml()
|
||||
.project
|
||||
.as_ref()
|
||||
.and_then(|project| project.requires_python.as_ref())
|
||||
}))?;
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
|
||||
let requires_python = if let Some(requires_python) = requires_python {
|
||||
if matches!(requires_python.bound(), Bound::Unbounded) {
|
||||
|
|
|
@ -7,7 +7,7 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep440_rs::Version;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
|
@ -75,6 +75,22 @@ pub(crate) enum ProjectError {
|
|||
RequiresPython(#[from] uv_resolver::RequiresPythonError),
|
||||
}
|
||||
|
||||
/// Compute the `Requires-Python` bound for the [`Workspace`].
|
||||
///
|
||||
/// For a [`Workspace`] with multiple packages, the `Requires-Python` bound is the union of the
|
||||
/// `Requires-Python` bounds of all the packages.
|
||||
pub(crate) fn find_requires_python(
|
||||
workspace: &Workspace,
|
||||
) -> Result<Option<RequiresPython>, uv_resolver::RequiresPythonError> {
|
||||
RequiresPython::union(workspace.packages().values().filter_map(|member| {
|
||||
member
|
||||
.pyproject_toml()
|
||||
.project
|
||||
.as_ref()
|
||||
.and_then(|project| project.requires_python.as_ref())
|
||||
}))
|
||||
}
|
||||
|
||||
/// Find the virtual environment for the current project.
|
||||
pub(crate) fn find_environment(
|
||||
workspace: &Workspace,
|
||||
|
@ -87,7 +103,7 @@ pub(crate) fn find_environment(
|
|||
pub(crate) fn interpreter_meets_requirements(
|
||||
interpreter: &Interpreter,
|
||||
requested_python: Option<&str>,
|
||||
requires_python: Option<&VersionSpecifiers>,
|
||||
requires_python: Option<&RequiresPython>,
|
||||
cache: &Cache,
|
||||
) -> bool {
|
||||
// `--python` has highest precedence, after that we check `requires_python` from
|
||||
|
@ -108,16 +124,12 @@ pub(crate) fn interpreter_meets_requirements(
|
|||
|
||||
if let Some(requires_python) = requires_python {
|
||||
if requires_python.contains(interpreter.python_version()) {
|
||||
debug!(
|
||||
"Interpreter meets the project `Requires-Python` constraint {}",
|
||||
requires_python
|
||||
);
|
||||
debug!("Interpreter meets the project `Requires-Python` constraint {requires_python}");
|
||||
return true;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Interpreter does not meet the project `Requires-Python` constraint {}",
|
||||
requires_python
|
||||
"Interpreter does not meet the project `Requires-Python` constraint {requires_python}"
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
@ -133,14 +145,17 @@ pub(crate) fn find_interpreter(
|
|||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<Interpreter, ProjectError> {
|
||||
let requires_python = workspace
|
||||
.root_member()
|
||||
.and_then(|root| root.project().requires_python.as_ref());
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
|
||||
// Read from the virtual environment first
|
||||
match find_environment(workspace, cache) {
|
||||
Ok(venv) => {
|
||||
if interpreter_meets_requirements(venv.interpreter(), python, requires_python, cache) {
|
||||
if interpreter_meets_requirements(
|
||||
venv.interpreter(),
|
||||
python,
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
return Ok(venv.into_interpreter());
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +165,8 @@ pub(crate) fn find_interpreter(
|
|||
|
||||
// Otherwise, find a system interpreter to use
|
||||
let interpreter = if let Some(request) = python.map(ToolchainRequest::parse).or(requires_python
|
||||
.as_ref()
|
||||
.map(RequiresPython::specifiers)
|
||||
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone()))))
|
||||
{
|
||||
Toolchain::find_requested(
|
||||
|
@ -163,7 +180,7 @@ pub(crate) fn find_interpreter(
|
|||
}?
|
||||
.into_interpreter();
|
||||
|
||||
if let Some(requires_python) = requires_python {
|
||||
if let Some(requires_python) = requires_python.as_ref() {
|
||||
if !requires_python.contains(interpreter.python_version()) {
|
||||
warn_user!(
|
||||
"The Python {} you requested with {} is incompatible with the requirement of the \
|
||||
|
@ -192,14 +209,17 @@ pub(crate) fn init_environment(
|
|||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<PythonEnvironment, ProjectError> {
|
||||
let requires_python = workspace
|
||||
.root_member()
|
||||
.and_then(|root| root.project().requires_python.as_ref());
|
||||
let requires_python = find_requires_python(workspace)?;
|
||||
|
||||
// Check if the environment exists and is sufficient
|
||||
match find_environment(workspace, cache) {
|
||||
Ok(venv) => {
|
||||
if interpreter_meets_requirements(venv.interpreter(), python, requires_python, cache) {
|
||||
if interpreter_meets_requirements(
|
||||
venv.interpreter(),
|
||||
python,
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
return Ok(venv);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue