mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Respect requires-python
in uv lock
(#4282)
We weren't using the common interface in `uv lock` because it didn't support finding an interpreter without touching the virtual environment. Here I refactor the project interface to support what we need and update `uv lock` to use the shared implementation.
This commit is contained in:
parent
e6d0c4d9fe
commit
3910b7a90c
6 changed files with 102 additions and 94 deletions
|
@ -41,13 +41,7 @@ pub(crate) async fn add(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Discover or create the virtual environment.
|
// Discover or create the virtual environment.
|
||||||
let venv = project::init_environment(
|
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||||
project.workspace(),
|
|
||||||
python.as_deref(),
|
|
||||||
preview,
|
|
||||||
cache,
|
|
||||||
printer,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let index_locations = IndexLocations::default();
|
let index_locations = IndexLocations::default();
|
||||||
let upgrade = Upgrade::default();
|
let upgrade = Upgrade::default();
|
||||||
|
|
|
@ -16,7 +16,7 @@ use uv_git::GitResolver;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
|
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
|
||||||
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, RequiresPython};
|
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, RequiresPython};
|
||||||
use uv_toolchain::{Interpreter, SystemPython, Toolchain, ToolchainRequest};
|
use uv_toolchain::Interpreter;
|
||||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
|
@ -43,28 +43,7 @@ pub(crate) async fn lock(
|
||||||
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
||||||
|
|
||||||
// Find an interpreter for the project
|
// Find an interpreter for the project
|
||||||
let interpreter = match project::find_environment(&workspace, cache) {
|
let interpreter = project::find_interpreter(&workspace, python.as_deref(), cache, printer)?;
|
||||||
Ok(environment) => {
|
|
||||||
let interpreter = environment.into_interpreter();
|
|
||||||
if let Some(python) = python.as_deref() {
|
|
||||||
let request = ToolchainRequest::parse(python);
|
|
||||||
if request.satisfied(&interpreter, cache) {
|
|
||||||
interpreter
|
|
||||||
} else {
|
|
||||||
let request = ToolchainRequest::parse(python);
|
|
||||||
Toolchain::find_requested(&request, SystemPython::Allowed, preview, cache)?
|
|
||||||
.into_interpreter()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
interpreter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(uv_toolchain::Error::NotFound(_)) => {
|
|
||||||
Toolchain::find(python.as_deref(), SystemPython::Allowed, preview, cache)?
|
|
||||||
.into_interpreter()
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Perform the lock operation.
|
// Perform the lock operation.
|
||||||
let root_project_name = workspace.root_member().and_then(|member| {
|
let root_project_name = workspace.root_member().and_then(|member| {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{IndexLocations, Resolution};
|
use distribution_types::{IndexLocations, Resolution};
|
||||||
use install_wheel_rs::linker::LinkMode;
|
use install_wheel_rs::linker::LinkMode;
|
||||||
use pep440_rs::Version;
|
use pep440_rs::{Version, VersionSpecifiers};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
|
@ -21,7 +21,9 @@ use uv_git::GitResolver;
|
||||||
use uv_installer::{SatisfiesResult, SitePackages};
|
use uv_installer::{SatisfiesResult, SitePackages};
|
||||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||||
use uv_resolver::{FlatIndex, InMemoryIndex, Options, RequiresPython};
|
use uv_resolver::{FlatIndex, InMemoryIndex, Options, RequiresPython};
|
||||||
use uv_toolchain::{PythonEnvironment, SystemPython, Toolchain, ToolchainRequest, VersionRequest};
|
use uv_toolchain::{
|
||||||
|
Interpreter, PythonEnvironment, SystemPython, Toolchain, ToolchainRequest, VersionRequest,
|
||||||
|
};
|
||||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
|
@ -81,70 +83,83 @@ pub(crate) fn find_environment(
|
||||||
PythonEnvironment::from_root(workspace.venv(), cache)
|
PythonEnvironment::from_root(workspace.venv(), cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a virtual environment for the current project.
|
/// Check if the given interpreter satisfies the project's requirements.
|
||||||
pub(crate) fn init_environment(
|
pub(crate) fn interpreter_meets_requirements(
|
||||||
|
interpreter: &Interpreter,
|
||||||
|
requested_python: Option<&str>,
|
||||||
|
requires_python: Option<&VersionSpecifiers>,
|
||||||
|
cache: &Cache,
|
||||||
|
) -> bool {
|
||||||
|
// `--python` has highest precedence, after that we check `requires_python` from
|
||||||
|
// `pyproject.toml`. If `--python` and `requires_python` are mutually incompatible,
|
||||||
|
// we'll fail at the build or at last the install step when we aren't able to install
|
||||||
|
// the editable wheel for the current project into the venv.
|
||||||
|
// TODO(konsti): Do we want to support a workspace python version requirement?
|
||||||
|
if let Some(python) = requested_python {
|
||||||
|
let request = ToolchainRequest::parse(python);
|
||||||
|
if request.satisfied(interpreter, cache) {
|
||||||
|
debug!("Interpreter meets the requested python {}", request);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Interpreter does not meet the request {}", request);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(requires_python) = requires_python {
|
||||||
|
if requires_python.contains(interpreter.python_version()) {
|
||||||
|
debug!(
|
||||||
|
"Interpreter meets the project `Requires-Python` constraint {}",
|
||||||
|
requires_python
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Interpreter does not meet the project `Requires-Python` constraint {}",
|
||||||
|
requires_python
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// No requirement to check
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the interpreter to use in the current project.
|
||||||
|
pub(crate) fn find_interpreter(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
python: Option<&str>,
|
python: Option<&str>,
|
||||||
preview: PreviewMode,
|
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
) -> Result<PythonEnvironment, ProjectError> {
|
) -> Result<Interpreter, ProjectError> {
|
||||||
let venv = workspace.root().join(".venv");
|
|
||||||
|
|
||||||
let requires_python = workspace
|
let requires_python = workspace
|
||||||
.root_member()
|
.root_member()
|
||||||
.and_then(|root| root.project().requires_python.as_ref());
|
.and_then(|root| root.project().requires_python.as_ref());
|
||||||
|
|
||||||
// Discover or create the virtual environment.
|
// Read from the virtual environment first
|
||||||
match PythonEnvironment::from_root(venv, cache) {
|
match find_environment(workspace, cache) {
|
||||||
Ok(venv) => {
|
Ok(venv) => {
|
||||||
// `--python` has highest precedence, after that we check `requires_python` from
|
if interpreter_meets_requirements(venv.interpreter(), python, requires_python, cache) {
|
||||||
// `pyproject.toml`. If `--python` and `requires_python` are mutually incompatible,
|
return Ok(venv.into_interpreter());
|
||||||
// we'll fail at the build or at last the install step when we aren't able to install
|
|
||||||
// the editable wheel for the current project into the venv.
|
|
||||||
// TODO(konsti): Do we want to support a workspace python version requirement?
|
|
||||||
let is_satisfied = if let Some(python) = python {
|
|
||||||
ToolchainRequest::parse(python).satisfied(venv.interpreter(), cache)
|
|
||||||
} else if let Some(requires_python) = requires_python {
|
|
||||||
requires_python.contains(venv.interpreter().python_version())
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_satisfied {
|
|
||||||
return Ok(venv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"Removing virtual environment at: {}",
|
|
||||||
venv.root().user_display().cyan()
|
|
||||||
)?;
|
|
||||||
fs_err::remove_dir_all(venv.root())
|
|
||||||
.context("Failed to remove existing virtual environment")?;
|
|
||||||
}
|
}
|
||||||
Err(uv_toolchain::Error::NotFound(_)) => {}
|
Err(uv_toolchain::Error::NotFound(_)) => {}
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Otherwise, find a system interpreter to use
|
||||||
let interpreter = if let Some(request) = python.map(ToolchainRequest::parse).or(requires_python
|
let interpreter = if let Some(request) = python.map(ToolchainRequest::parse).or(requires_python
|
||||||
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone()))))
|
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone()))))
|
||||||
{
|
{
|
||||||
Toolchain::find_requested(
|
Toolchain::find_requested(
|
||||||
&request,
|
&request,
|
||||||
// Otherwise we'll try to use the venv we just deleted.
|
|
||||||
SystemPython::Required,
|
SystemPython::Required,
|
||||||
preview,
|
PreviewMode::Enabled,
|
||||||
cache,
|
cache,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Toolchain::find(
|
Toolchain::find(None, SystemPython::Required, PreviewMode::Enabled, cache)
|
||||||
None,
|
|
||||||
// Otherwise we'll try to use the venv we just deleted.
|
|
||||||
SystemPython::Required,
|
|
||||||
preview,
|
|
||||||
cache,
|
|
||||||
)
|
|
||||||
}?
|
}?
|
||||||
.into_interpreter();
|
.into_interpreter();
|
||||||
|
|
||||||
|
@ -167,6 +182,43 @@ pub(crate) fn init_environment(
|
||||||
interpreter.sys_executable().user_display().cyan()
|
interpreter.sys_executable().user_display().cyan()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
Ok(interpreter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a virtual environment for the current project.
|
||||||
|
pub(crate) fn init_environment(
|
||||||
|
workspace: &Workspace,
|
||||||
|
python: Option<&str>,
|
||||||
|
cache: &Cache,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<PythonEnvironment, ProjectError> {
|
||||||
|
let requires_python = workspace
|
||||||
|
.root_member()
|
||||||
|
.and_then(|root| root.project().requires_python.as_ref());
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return Ok(venv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the existing virtual environment if it doesn't meet the requirements
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"Removing virtual environment at: {}",
|
||||||
|
venv.root().user_display().cyan()
|
||||||
|
)?;
|
||||||
|
fs_err::remove_dir_all(venv.root())
|
||||||
|
.context("Failed to remove existing virtual environment")?;
|
||||||
|
}
|
||||||
|
Err(uv_toolchain::Error::NotFound(_)) => {}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find an interpreter to create the environment with
|
||||||
|
let interpreter = find_interpreter(workspace, python, cache, printer)?;
|
||||||
|
|
||||||
let venv = workspace.venv();
|
let venv = workspace.venv();
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
|
|
|
@ -44,13 +44,7 @@ pub(crate) async fn remove(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Discover or create the virtual environment.
|
// Discover or create the virtual environment.
|
||||||
let venv = project::init_environment(
|
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||||
project.workspace(),
|
|
||||||
python.as_deref(),
|
|
||||||
preview,
|
|
||||||
cache,
|
|
||||||
printer,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let index_locations = IndexLocations::default();
|
let index_locations = IndexLocations::default();
|
||||||
let upgrade = Upgrade::None;
|
let upgrade = Upgrade::None;
|
||||||
|
|
|
@ -62,13 +62,8 @@ pub(crate) async fn run(
|
||||||
} else {
|
} else {
|
||||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
||||||
};
|
};
|
||||||
let venv = project::init_environment(
|
let venv =
|
||||||
project.workspace(),
|
project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||||
python.as_deref(),
|
|
||||||
preview,
|
|
||||||
cache,
|
|
||||||
printer,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Lock and sync the environment.
|
// Lock and sync the environment.
|
||||||
let root_project_name = project
|
let root_project_name = project
|
||||||
|
|
|
@ -43,13 +43,7 @@ pub(crate) async fn sync(
|
||||||
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
|
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
|
||||||
|
|
||||||
// Discover or create the virtual environment.
|
// Discover or create the virtual environment.
|
||||||
let venv = project::init_environment(
|
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||||
project.workspace(),
|
|
||||||
python.as_deref(),
|
|
||||||
preview,
|
|
||||||
cache,
|
|
||||||
printer,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Read the lockfile.
|
// Read the lockfile.
|
||||||
let lock: Lock = {
|
let lock: Lock = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue