diff --git a/Cargo.lock b/Cargo.lock index 35b14d51a..2134aa44c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4733,6 +4733,7 @@ name = "uv-requirements" version = "0.1.0" dependencies = [ "anyhow", + "cache-key", "configparser", "console", "ctrlc", diff --git a/crates/distribution-types/src/index_url.rs b/crates/distribution-types/src/index_url.rs index 86b338ecf..93c176b97 100644 --- a/crates/distribution-types/src/index_url.rs +++ b/crates/distribution-types/src/index_url.rs @@ -28,7 +28,7 @@ pub enum IndexUrl { impl IndexUrl { /// Return the raw URL for the index. - pub(crate) fn url(&self) -> &Url { + pub fn url(&self) -> &Url { match self { Self::Pypi(url) => url.raw(), Self::Url(url) => url.raw(), diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index c37c4f0b7..572c236dd 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -10,6 +10,7 @@ authors.workspace = true license.workspace = true [dependencies] +cache-key = { workspace = true } distribution-filename = { workspace = true } distribution-types = { workspace = true } pep508_rs = { workspace = true } diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 16c4aaa99..8dc96d551 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -5,7 +5,7 @@ use indexmap::IndexMap; use rustc_hash::FxHashSet; use tracing::{instrument, Level}; -use crate::{ExtrasSpecification, RequirementsSource}; +use cache_key::CanonicalUrl; use distribution_types::{FlatIndexLocation, IndexUrl}; use pep508_rs::{Requirement, RequirementsTxtRequirement}; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; @@ -14,6 +14,8 @@ use uv_fs::Simplified; use uv_normalize::{ExtraName, PackageName}; use uv_warnings::warn_user; +use crate::{ExtrasSpecification, RequirementsSource}; + #[derive(Debug, Default)] pub struct RequirementsSpecification { /// The name of the project specifying requirements. @@ -205,13 +207,15 @@ impl RequirementsSpecification { spec.project = source.project; } - if let Some(url) = source.index_url { + if let Some(index_url) = source.index_url { if let Some(existing) = spec.index_url { - return Err(anyhow::anyhow!( - "Multiple index URLs specified: `{existing}` vs.` {url}", - )); + if CanonicalUrl::new(index_url.url()) != CanonicalUrl::new(existing.url()) { + return Err(anyhow::anyhow!( + "Multiple index URLs specified: `{existing}` vs. `{index_url}`", + )); + } } - spec.index_url = Some(url); + spec.index_url = Some(index_url); } spec.no_index |= source.no_index; spec.extra_index_urls.extend(source.extra_index_urls); @@ -236,13 +240,15 @@ impl RequirementsSpecification { spec.constraints.extend(source.constraints); spec.constraints.extend(source.overrides); - if let Some(url) = source.index_url { + if let Some(index_url) = source.index_url { if let Some(existing) = spec.index_url { - return Err(anyhow::anyhow!( - "Multiple index URLs specified: `{existing}` vs.` {url}", - )); + if CanonicalUrl::new(index_url.url()) != CanonicalUrl::new(existing.url()) { + return Err(anyhow::anyhow!( + "Multiple index URLs specified: `{existing}` vs. `{index_url}`", + )); + } } - spec.index_url = Some(url); + spec.index_url = Some(index_url); } spec.no_index |= source.no_index; spec.extra_index_urls.extend(source.extra_index_urls); @@ -267,13 +273,15 @@ impl RequirementsSpecification { spec.overrides.extend(source.constraints); spec.overrides.extend(source.overrides); - if let Some(url) = source.index_url { + if let Some(index_url) = source.index_url { if let Some(existing) = spec.index_url { - return Err(anyhow::anyhow!( - "Multiple index URLs specified: `{existing}` vs.` {url}", - )); + if CanonicalUrl::new(index_url.url()) != CanonicalUrl::new(existing.url()) { + return Err(anyhow::anyhow!( + "Multiple index URLs specified: `{existing}` vs. `{index_url}`", + )); + } } - spec.index_url = Some(url); + spec.index_url = Some(index_url); } spec.no_index |= source.no_index; spec.extra_index_urls.extend(source.extra_index_urls); diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 1fc08d1bb..b973d0f85 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3812,7 +3812,7 @@ fn index_url_requirements_txt() -> Result<()> { Ok(()) } -/// Raise an error when multiple `requirements.txt` files include `--index-url` flags. +/// Raise an error when multiple `requirements.txt` files include different `--index-url` flags. #[test] fn conflicting_index_urls_requirements_txt() -> Result<()> { let context = TestContext::new("3.12"); @@ -3831,7 +3831,35 @@ fn conflicting_index_urls_requirements_txt() -> Result<()> { ----- stdout ----- ----- stderr ----- - error: Multiple index URLs specified: `https://google.com/` vs.` https://wikipedia.org/ + error: Multiple index URLs specified: `https://google.com/` vs. `https://wikipedia.org/` + "### + ); + + Ok(()) +} + +/// Doesn't raise an error when multiple `requirements.txt` files include matching `--index-url` flags. +#[test] +fn matching_index_urls_requirements_txt() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("--index-url https://pypi.org/simple")?; + + let constraints_in = context.temp_dir.child("constraints.in"); + constraints_in.write_str("--index-url https://pypi.org/simple")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--constraint") + .arg("constraints.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in --constraint constraints.in + + ----- stderr ----- + Resolved 0 packages in [TIME] "### );