mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 18:38:21 +00:00
Warn when duplicate index names found in single file (#11824)
<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary This pull request introduces validation for unique index names in the `tool.uv.index` field and adds corresponding tests to ensure the functionality. The most important changes include adding a custom deserializer function, updating the `ToolUv` struct to use the new deserializer, and adding tests to verify the behavior. Validation and deserialization: * [`crates/uv-workspace/src/pyproject.rs`](diffhunk://#diff-e12cd255985adfd45ab06f398cb420d2f543841ccbeea4175ccf827aa9215b9dR283-R311): Added a custom deserializer function `deserialize_index_vec` to validate that index names in the `tool.uv.index` field are unique. * [`crates/uv-workspace/src/pyproject.rs`](diffhunk://#diff-e12cd255985adfd45ab06f398cb420d2f543841ccbeea4175ccf827aa9215b9dR374): Updated the `ToolUv` struct to use the `deserialize_index_vec` function for the `index` field. Testing: * [`crates/uv/tests/it/lock.rs`](diffhunk://#diff-82edd36151736f44055f699a34c8b19a63ffc4cf3c86bf5fb34d69f8ac88a957R15336): Added a test `lock_repeat_named_index` to verify that duplicate index names result in an error. [[1]](diffhunk://#diff-82edd36151736f44055f699a34c8b19a63ffc4cf3c86bf5fb34d69f8ac88a957R15336) [[2]](diffhunk://#diff-82edd36151736f44055f699a34c8b19a63ffc4cf3c86bf5fb34d69f8ac88a957R15360-R15402) * [`crates/uv/tests/it/lock.rs`](diffhunk://#diff-82edd36151736f44055f699a34c8b19a63ffc4cf3c86bf5fb34d69f8ac88a957R15360-R15402): Added a test `lock_unique_named_index` to verify that unique index names result in successful lock file generation. Schema update: * [`uv.schema.json`](diffhunk://#diff-c669473b258a19ba6d3557d0369126773b68b27171989f265333a77bc5cb935bR205): Updated the schema to set the default value of the `index` field to `null`. Fixes #11804 ## Test Plan ### Steps to reproduce and verify the fix: 1. Clone the repository and checkout the feature branch ```bash git clone https://github.com/astral-sh/uv.git cd uv git checkout feature/warn-duplicate-index-names ``` 2. Build the modified binary ```bash cargo build ``` 3. Create a test project using the system installed uv ```bash uv init uv-test cd uv-test ``` 4. Manually edit pyproject.toml to add duplicate index names ```toml [[tool.uv.index]] name = "alpha_b" url = "<omitted>" [[tool.uv.index]] name = "alpha_b" url = "<omitted>" ``` 5. Try to add a package using the modified binary ```bash ../target/debug/uv add numpy ``` ### Results Before: use release binary  After: use self build binary  Now when attempting to use a pyproject.toml with duplicate index names, the modified binary correctly detects the issue and produces an error message: ``` error: Failed to parse: `pyproject.toml` Caused by: TOML parse error at line 9, column 1 | 9 | [[tool.uv.index]] | ^^^^^^^^^^^^^^^^^ duplicate index name `alpha_b` ``` --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
4d9c861506
commit
ad86005e9a
3 changed files with 68 additions and 5 deletions
|
@ -14,6 +14,7 @@ use std::str::FromStr;
|
|||
|
||||
use glob::Pattern;
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use serde::{de::IntoDeserializer, de::SeqAccess, Deserialize, Deserializer, Serialize};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
@ -280,6 +281,30 @@ pub struct Tool {
|
|||
pub uv: Option<ToolUv>,
|
||||
}
|
||||
|
||||
/// Validates that index names in the `tool.uv.index` field are unique.
|
||||
///
|
||||
/// This custom deserializer function checks for duplicate index names
|
||||
/// and returns an error if any duplicates are found.
|
||||
fn deserialize_index_vec<'de, D>(deserializer: D) -> Result<Option<Vec<Index>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let indexes = Option::<Vec<Index>>::deserialize(deserializer)?;
|
||||
if let Some(indexes) = indexes.as_ref() {
|
||||
let mut seen_names = FxHashSet::with_capacity_and_hasher(indexes.len(), FxBuildHasher);
|
||||
for index in indexes {
|
||||
if let Some(name) = index.name.as_ref() {
|
||||
if !seen_names.insert(name) {
|
||||
return Err(serde::de::Error::custom(format!(
|
||||
"duplicate index name `{name}`"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(indexes)
|
||||
}
|
||||
|
||||
// NOTE(charlie): When adding fields to this struct, mark them as ignored on `Options` in
|
||||
// `crates/uv-settings/src/settings.rs`.
|
||||
#[derive(Deserialize, OptionsMetadata, Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -342,6 +367,7 @@ pub struct ToolUv {
|
|||
url = "https://download.pytorch.org/whl/cu121"
|
||||
"#
|
||||
)]
|
||||
#[serde(deserialize_with = "deserialize_index_vec", default)]
|
||||
pub index: Option<Vec<Index>>,
|
||||
|
||||
/// The workspace definition for the project, if any.
|
||||
|
|
|
@ -15328,11 +15328,7 @@ fn lock_named_index_cli() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// If a name is reused, the higher-priority index should "overwrite" the lower-priority index.
|
||||
/// In other words, the lower-priority index should be ignored entirely during implicit resolution.
|
||||
///
|
||||
/// In this test, we should use PyPI (the default index) and ignore `https://example.com` entirely.
|
||||
/// (Querying `https://example.com` would fail with a 500.)
|
||||
/// If a name is reused, within a single file, we should raise an error.
|
||||
#[test]
|
||||
fn lock_repeat_named_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
@ -15356,6 +15352,46 @@ fn lock_repeat_named_index() -> Result<()> {
|
|||
"#,
|
||||
)?;
|
||||
|
||||
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 | [[tool.uv.index]]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
duplicate index name `pytorch`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_unique_named_index() -> 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 = ["iniconfig"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "pytorch"
|
||||
url = "https://astral-sh.github.io/pytorch-mirror/whl/cu121"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "example"
|
||||
url = "https://example.com"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Fall back to PyPI, since `iniconfig` doesn't exist on the PyTorch index.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue