This commit is contained in:
liam 2025-10-04 23:03:32 +02:00 committed by GitHub
commit a8fe2e3faf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 155 additions and 12 deletions

View file

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use uv_configuration::SourceStrategy;
use uv_distribution_types::{IndexLocations, Requirement};
use uv_distribution_types::{IndexLocations, Requirement, RequiresPython};
use uv_normalize::{GroupName, PackageName};
use uv_workspace::dependency_groups::FlatDependencyGroups;
use uv_workspace::pyproject::{Sources, ToolUvSources};
@ -45,7 +45,13 @@ use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
#[derive(Debug, Clone)]
pub struct SourcedDependencyGroups {
pub name: Option<PackageName>,
pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
pub dependency_groups: BTreeMap<GroupName, SourcedDependencyGroup>,
}
#[derive(Debug, Clone)]
pub struct SourcedDependencyGroup {
pub requirements: Box<[Requirement]>,
pub requires_python: Option<RequiresPython>,
}
impl SourcedDependencyGroups {
@ -98,12 +104,23 @@ impl SourcedDependencyGroups {
dependency_groups: dependency_groups
.into_iter()
.map(|(name, group)| {
let requires_python = group
.requires_python
.as_ref()
.map(RequiresPython::from_specifiers);
let requirements = group
.requirements
.into_iter()
.map(Requirement::from)
.collect();
(name, requirements)
(
name,
SourcedDependencyGroup {
requirements,
requires_python,
},
)
})
.collect(),
});
@ -138,6 +155,11 @@ impl SourcedDependencyGroups {
let dependency_groups = dependency_groups
.into_iter()
.map(|(name, group)| {
let requires_python = group
.requires_python
.as_ref()
.map(RequiresPython::from_specifiers);
let requirements = group
.requirements
.into_iter()
@ -167,7 +189,13 @@ impl SourcedDependencyGroups {
})
})
.collect::<Result<Box<_>, _>>()?;
Ok::<(GroupName, Box<_>), MetadataError>((name, requirements))
Ok::<(GroupName, SourcedDependencyGroup), MetadataError>((
name,
SourcedDependencyGroup {
requirements,
requires_python,
},
))
})
.collect::<Result<BTreeMap<_, _>, _>>()?;

View file

@ -15,7 +15,7 @@ pub use manifest::Manifest;
pub use options::{Flexibility, Options, OptionsBuilder};
pub use preferences::{Preference, PreferenceError, Preferences};
pub use prerelease::PrereleaseMode;
pub use python_requirement::PythonRequirement;
pub use python_requirement::{PythonRequirement, PythonRequirementSource};
pub use resolution::{
AnnotationStyle, ConflictingDistributionError, DisplayResolutionGraph, ResolverOutput,
};

View file

@ -27,6 +27,7 @@ use uv_fs::Simplified;
use uv_install_wheel::LinkMode;
use uv_installer::{InstallationStrategy, Plan, Planner, Preparer, SitePackages};
use uv_normalize::PackageName;
use uv_pep440::Operator;
use uv_pep508::{MarkerEnvironment, RequirementOrigin};
use uv_platform_tags::Tags;
use uv_preview::Preview;
@ -235,13 +236,62 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
// Apply dependency-groups
for (group_name, group) in &metadata.dependency_groups {
if groups.contains(group_name) {
requirements.extend(group.iter().cloned().map(|group| Requirement {
origin: Some(RequirementOrigin::Group(
pyproject_path.clone(),
metadata.name.clone(),
group_name.clone(),
)),
..group
if let Some(requires_python) = group.requires_python.as_ref() {
if !python_requirement
.target()
.is_contained_by(requires_python.specifiers())
{
let required_spec = requires_python.specifiers().to_string();
let active_spec = python_requirement.target().specifiers().to_string();
let interpreter_version = python_requirement.exact().to_string();
let suggested_version = requires_python
.range()
.lower()
.specifier()
.and_then(|specifier| {
let (operator, version) = specifier.into_parts();
match operator {
Operator::GreaterThanEqual
| Operator::Equal
| Operator::ExactEqual
| Operator::EqualStar
| Operator::TildeEqual => Some(version),
_ => None,
}
});
let mut message = format!(
"Dependency group `{group_name}` in `{}` requires Python `{required_spec}`, but uv is resolving for Python `{active_spec}` (current interpreter: `{interpreter_version}`).",
pyproject_path.user_display()
);
if let Some(ref version) = suggested_version {
let suggested_python = version.to_string();
write!(
message,
"{}",
&format!(
" Re-run with `--python {suggested_python}` to target a compatible Python version."
)
)?;
}
return Err(anyhow!(message).into());
}
}
requirements.extend(group.requirements.iter().cloned().map(|group| {
Requirement {
origin: Some(RequirementOrigin::Group(
pyproject_path.clone(),
metadata.name.clone(),
group_name.clone(),
)),
..group
}
}));
}
}

View file

@ -16070,6 +16070,71 @@ fn project_and_group_workspace() -> Result<()> {
Ok(())
}
#[test]
fn group_requires_python_incompatible_with_interpreter() -> Result<()> {
let context = TestContext::new("3.13");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "requires-python"
version = "0.1.0"
[tool.uv.dependency-groups.ml1]
requires-python = ">=3.14"
[dependency-groups]
ml1 = ["tqdm"]
"#,
)?;
uv_snapshot!(context.filters(), context.pip_compile()
.arg("--group").arg("pyproject.toml:ml1"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Dependency group `ml1` in `pyproject.toml` requires Python `>=3.14`, but uv is resolving for Python `>=3.13.[X]` (current interpreter: `3.13.[X]`). Re-run with `--python 3.14` to target a compatible Python version.
");
Ok(())
}
#[test]
fn group_requires_python_incompatible_with_python_flag() -> Result<()> {
let context = TestContext::new_with_versions(&["3.12", "3.13"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "requires-python"
version = "0.1.0"
[tool.uv.dependency-groups.ml1]
requires-python = ">=3.13"
[dependency-groups]
ml1 = ["tqdm"]
"#,
)?;
uv_snapshot!(context.filters(), context.pip_compile()
.arg("--group").arg("pyproject.toml:ml1")
.arg("--python").arg("3.12"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Dependency group `ml1` in `pyproject.toml` requires Python `>=3.13`, but uv is resolving for Python `>=3.12` (current interpreter: `3.12.[X]`). Re-run with `--python 3.13` to target a compatible Python version.
");
Ok(())
}
#[test]
fn directory_and_group() -> Result<()> {
// Checking that --directory is handled properly with --group