mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-06 16:50:26 +00:00
Merge 8f6b1f528f
into 00d3aa3780
This commit is contained in:
commit
a8fe2e3faf
4 changed files with 155 additions and 12 deletions
|
@ -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<_, _>, _>>()?;
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue