mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +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.
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref(),
|
||||
preview,
|
||||
cache,
|
||||
printer,
|
||||
)?;
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
|
||||
let index_locations = IndexLocations::default();
|
||||
let upgrade = Upgrade::default();
|
||||
|
|
|
@ -16,7 +16,7 @@ use uv_git::GitResolver;
|
|||
use uv_normalize::PackageName;
|
||||
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
|
||||
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_warnings::warn_user;
|
||||
|
||||
|
@ -43,28 +43,7 @@ pub(crate) async fn lock(
|
|||
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
||||
|
||||
// Find an interpreter for the project
|
||||
let interpreter = match project::find_environment(&workspace, cache) {
|
||||
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()),
|
||||
};
|
||||
let interpreter = project::find_interpreter(&workspace, python.as_deref(), cache, printer)?;
|
||||
|
||||
// Perform the lock operation.
|
||||
let root_project_name = workspace.root_member().and_then(|member| {
|
||||
|
|
|
@ -7,7 +7,7 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::{IndexLocations, Resolution};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use pep440_rs::Version;
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
|
@ -21,7 +21,9 @@ use uv_git::GitResolver;
|
|||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
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_warnings::warn_user;
|
||||
|
||||
|
@ -81,70 +83,83 @@ pub(crate) fn find_environment(
|
|||
PythonEnvironment::from_root(workspace.venv(), cache)
|
||||
}
|
||||
|
||||
/// Initialize a virtual environment for the current project.
|
||||
pub(crate) fn init_environment(
|
||||
/// Check if the given interpreter satisfies the project's requirements.
|
||||
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,
|
||||
python: Option<&str>,
|
||||
preview: PreviewMode,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<PythonEnvironment, ProjectError> {
|
||||
let venv = workspace.root().join(".venv");
|
||||
|
||||
) -> Result<Interpreter, ProjectError> {
|
||||
let requires_python = workspace
|
||||
.root_member()
|
||||
.and_then(|root| root.project().requires_python.as_ref());
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
match PythonEnvironment::from_root(venv, cache) {
|
||||
// Read from the virtual environment first
|
||||
match find_environment(workspace, cache) {
|
||||
Ok(venv) => {
|
||||
// `--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?
|
||||
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);
|
||||
if interpreter_meets_requirements(venv.interpreter(), python, requires_python, cache) {
|
||||
return Ok(venv.into_interpreter());
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise, find a system interpreter to use
|
||||
let interpreter = if let Some(request) = python.map(ToolchainRequest::parse).or(requires_python
|
||||
.map(|specifiers| ToolchainRequest::Version(VersionRequest::Range(specifiers.clone()))))
|
||||
{
|
||||
Toolchain::find_requested(
|
||||
&request,
|
||||
// Otherwise we'll try to use the venv we just deleted.
|
||||
SystemPython::Required,
|
||||
preview,
|
||||
PreviewMode::Enabled,
|
||||
cache,
|
||||
)
|
||||
} else {
|
||||
Toolchain::find(
|
||||
None,
|
||||
// Otherwise we'll try to use the venv we just deleted.
|
||||
SystemPython::Required,
|
||||
preview,
|
||||
cache,
|
||||
)
|
||||
Toolchain::find(None, SystemPython::Required, PreviewMode::Enabled, cache)
|
||||
}?
|
||||
.into_interpreter();
|
||||
|
||||
|
@ -167,6 +182,43 @@ pub(crate) fn init_environment(
|
|||
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();
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
|
|
|
@ -44,13 +44,7 @@ pub(crate) async fn remove(
|
|||
)?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref(),
|
||||
preview,
|
||||
cache,
|
||||
printer,
|
||||
)?;
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
|
||||
let index_locations = IndexLocations::default();
|
||||
let upgrade = Upgrade::None;
|
||||
|
|
|
@ -62,13 +62,8 @@ pub(crate) async fn run(
|
|||
} else {
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
||||
};
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref(),
|
||||
preview,
|
||||
cache,
|
||||
printer,
|
||||
)?;
|
||||
let venv =
|
||||
project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
|
||||
// Lock and sync the environment.
|
||||
let root_project_name = project
|
||||
|
|
|
@ -43,13 +43,7 @@ pub(crate) async fn sync(
|
|||
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref(),
|
||||
preview,
|
||||
cache,
|
||||
printer,
|
||||
)?;
|
||||
let venv = project::init_environment(project.workspace(), python.as_deref(), cache, printer)?;
|
||||
|
||||
// Read the lockfile.
|
||||
let lock: Lock = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue