mirror of https://github.com/astral-sh/uv
Error on duplicate PEP 735 dependency groups (#8390)
## Summary Part of: https://github.com/astral-sh/uv/pull/8272.
This commit is contained in:
parent
384f4459a1
commit
4d134a4ffe
|
|
@ -46,7 +46,7 @@ pub struct PyProjectToml {
|
||||||
/// Tool-specific metadata.
|
/// Tool-specific metadata.
|
||||||
pub tool: Option<Tool>,
|
pub tool: Option<Tool>,
|
||||||
/// Non-project dependency groups, as defined in PEP 735.
|
/// Non-project dependency groups, as defined in PEP 735.
|
||||||
pub dependency_groups: Option<BTreeMap<GroupName, Vec<DependencyGroupSpecifier>>>,
|
pub dependency_groups: Option<DependencyGroups>,
|
||||||
/// The raw unserialized document.
|
/// The raw unserialized document.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub raw: String,
|
pub raw: String,
|
||||||
|
|
@ -540,6 +540,79 @@ impl Deref for SerdePattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[cfg_attr(test, derive(Serialize))]
|
||||||
|
pub struct DependencyGroups(BTreeMap<GroupName, Vec<DependencyGroupSpecifier>>);
|
||||||
|
|
||||||
|
impl DependencyGroups {
|
||||||
|
/// Returns the names of the dependency groups.
|
||||||
|
pub fn keys(&self) -> impl Iterator<Item = &GroupName> {
|
||||||
|
self.0.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the dependency group with the given name.
|
||||||
|
pub fn get(&self, group: &GroupName) -> Option<&Vec<DependencyGroupSpecifier>> {
|
||||||
|
self.0.get(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the dependency groups.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&GroupName, &Vec<DependencyGroupSpecifier>)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a DependencyGroups {
|
||||||
|
type Item = (&'a GroupName, &'a Vec<DependencyGroupSpecifier>);
|
||||||
|
type IntoIter = std::collections::btree_map::Iter<'a, GroupName, Vec<DependencyGroupSpecifier>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that all keys in the TOML table are unique.
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for DependencyGroups {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct GroupVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for GroupVisitor {
|
||||||
|
type Value = DependencyGroups;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a table with unique dependency group names")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut sources = BTreeMap::new();
|
||||||
|
while let Some((key, value)) =
|
||||||
|
access.next_entry::<GroupName, Vec<DependencyGroupSpecifier>>()?
|
||||||
|
{
|
||||||
|
match sources.entry(key) {
|
||||||
|
std::collections::btree_map::Entry::Occupied(entry) => {
|
||||||
|
return Err(serde::de::Error::custom(format!(
|
||||||
|
"duplicate dependency group: `{}`",
|
||||||
|
entry.key()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
std::collections::btree_map::Entry::Vacant(entry) => {
|
||||||
|
entry.insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(DependencyGroups(sources))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_map(GroupVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(rename_all = "kebab-case", try_from = "SourcesWire")]
|
#[serde(rename_all = "kebab-case", try_from = "SourcesWire")]
|
||||||
|
|
|
||||||
|
|
@ -16635,6 +16635,42 @@ fn lock_group_invalid_entry_group_name() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lock_group_invalid_duplicate_group_name() -> 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 = ["typing-extensions"]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
foo-bar = []
|
||||||
|
foo_bar = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Failed to parse: `pyproject.toml`
|
||||||
|
Caused by: TOML parse error at line 8, column 9
|
||||||
|
|
|
||||||
|
8 | [dependency-groups]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
duplicate dependency group: `foo-bar`
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lock_group_invalid_entry_table() -> Result<()> {
|
fn lock_group_invalid_entry_table() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue