Add a dedicated error for `include = "dev"` with `tool.uv.dev-dependencies` (#9173)

## Summary

This isn't really spec-compliant, so we already don't allow it -- this
just adds a better error message and an explicit test for it.
This commit is contained in:
Charlie Marsh 2024-11-17 13:22:04 -05:00 committed by GitHub
parent fb3f365d10
commit 12266f8f81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 61 additions and 4 deletions

View File

@ -132,7 +132,8 @@ impl RequiresDist {
// Resolve any `include-group` entries in `dependency-groups`.
let dependency_groups =
FlatDependencyGroups::from_dependency_groups(&dependency_groups)?
FlatDependencyGroups::from_dependency_groups(&dependency_groups)
.map_err(|err| err.with_dev_dependencies(dev_dependencies))?
.into_iter()
.chain(
// Only add the `dev` group if `dev-dependencies` is defined.

View File

@ -114,7 +114,8 @@ impl<'env> InstallTarget<'env> {
// Merge any overlapping groups.
let mut map = BTreeMap::new();
for (name, dependencies) in
FlatDependencyGroups::from_dependency_groups(&dependency_groups)?
FlatDependencyGroups::from_dependency_groups(&dependency_groups)
.map_err(|err| err.with_dev_dependencies(dev_dependencies))?
.into_iter()
.chain(
// Only add the `dev` group if `dev-dependencies` is defined.

View File

@ -4,7 +4,7 @@ use std::str::FromStr;
use thiserror::Error;
use tracing::warn;
use uv_normalize::GroupName;
use uv_normalize::{GroupName, DEV_DEPENDENCIES};
use uv_pep508::Pep508Error;
use uv_pypi_types::VerbatimParsedUrl;
@ -126,10 +126,30 @@ pub enum DependencyGroupError {
),
#[error("Failed to find group `{0}` included by `{1}`")]
GroupNotFound(GroupName, GroupName),
#[error("Group `{0}` includes the `dev` group (`include = \"dev\"`), but only `tool.uv.dev-dependencies` was found. To reference the `dev` group via an `include`, remove the `tool.uv.dev-dependencies` section and add any development dependencies to the `dev` entry in the `[dependency-groups]` table instead.")]
DevGroupInclude(GroupName),
#[error("Detected a cycle in `dependency-groups`: {0}")]
DependencyGroupCycle(Cycle),
}
impl DependencyGroupError {
/// Enrich a [`DependencyGroupError`] with the `tool.uv.dev-dependencies` metadata, if applicable.
#[must_use]
pub fn with_dev_dependencies(
self,
dev_dependencies: Option<&Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
) -> Self {
match self {
DependencyGroupError::GroupNotFound(group, parent)
if dev_dependencies.is_some() && group == *DEV_DEPENDENCIES =>
{
DependencyGroupError::DevGroupInclude(parent)
}
_ => self,
}
}
}
/// A cycle in the `dependency-groups` table.
#[derive(Debug)]
pub struct Cycle(Vec<GroupName>);

View File

@ -343,7 +343,8 @@ impl Workspace {
// Resolve any `include-group` entries in `dependency-groups`.
let dependency_groups =
FlatDependencyGroups::from_dependency_groups(&dependency_groups)?;
FlatDependencyGroups::from_dependency_groups(&dependency_groups)
.map_err(|err| err.with_dev_dependencies(dev_dependencies))?;
// Concatenate the two sets of requirements.
let dev_dependencies = dependency_groups

View File

@ -19096,6 +19096,40 @@ fn lock_group_include_cycle() -> Result<()> {
Ok(())
}
#[test]
fn lock_group_include_dev() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[tool.uv]
dev-dependencies = ["anyio"]
[dependency-groups]
foo = ["typing-extensions", {include-group = "dev"}]
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to build `project @ file://[TEMP_DIR]/`
Group `foo` includes the `dev` group (`include = "dev"`), but only `tool.uv.dev-dependencies` was found. To reference the `dev` group via an `include`, remove the `tool.uv.dev-dependencies` section and add any development dependencies to the `dev` entry in the `[dependency-groups]` table instead.
"###);
Ok(())
}
#[test]
fn lock_group_include_missing() -> Result<()> {
let context = TestContext::new("3.12");