From d213730b90adf404285e61175e022855886af812 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 17 Sep 2024 20:53:43 -0400 Subject: [PATCH] Respect declared sources --- crates/distribution-types/src/index_url.rs | 34 +++++++++++++++++-- crates/distribution-types/src/named_index.rs | 4 +-- crates/pep508-rs/src/verbatim_url.rs | 5 +++ crates/uv-cli/src/options.rs | 2 ++ .../uv-distribution/src/metadata/lowering.rs | 4 +-- crates/uv-settings/src/settings.rs | 24 ++++++++++++- crates/uv/src/commands/pip/compile.rs | 9 +++-- crates/uv/src/commands/pip/install.rs | 9 +++-- crates/uv/src/commands/pip/sync.rs | 9 +++-- crates/uv/src/settings.rs | 4 +++ 10 files changed, 90 insertions(+), 14 deletions(-) diff --git a/crates/distribution-types/src/index_url.rs b/crates/distribution-types/src/index_url.rs index 91077db0c8bb..5aba8756ecdb 100644 --- a/crates/distribution-types/src/index_url.rs +++ b/crates/distribution-types/src/index_url.rs @@ -11,7 +11,7 @@ use url::{ParseError, Url}; use pep508_rs::{VerbatimUrl, VerbatimUrlError}; -use crate::Verbatim; +use crate::{IndexSource, Verbatim}; static PYPI_URL: LazyLock = LazyLock::new(|| Url::parse("https://pypi.org/simple").unwrap()); @@ -55,6 +55,14 @@ impl IndexUrl { } } + pub fn into_url(self) -> Url { + match self { + Self::Pypi(url) => url.into_url(), + Self::Url(url) => url.into_url(), + Self::Path(url) => url.into_url(), + } + } + /// Return the redacted URL for the index, omitting any sensitive credentials. pub fn redacted(&self) -> Cow<'_, Url> { let url = self.url(); @@ -292,6 +300,7 @@ impl From for FlatIndexLocation { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct IndexLocations { + sources: Vec, index: Option, extra_index: Vec, flat_index: Vec, @@ -302,6 +311,7 @@ impl Default for IndexLocations { /// By default, use the `PyPI` index. fn default() -> Self { Self { + sources: Vec::new(), index: Some(DEFAULT_INDEX_URL.clone()), extra_index: Vec::new(), flat_index: Vec::new(), @@ -313,12 +323,14 @@ impl Default for IndexLocations { impl IndexLocations { /// Determine the index URLs to use for fetching packages. pub fn new( + sources: Vec, index: Option, extra_index: Vec, flat_index: Vec, no_index: bool, ) -> Self { Self { + sources, index, extra_index, flat_index, @@ -335,12 +347,14 @@ impl IndexLocations { #[must_use] pub fn combine( self, + sources: Vec, index: Option, extra_index: Vec, flat_index: Vec, no_index: bool, ) -> Self { Self { + sources: self.sources.into_iter().chain(sources).collect(), index: self.index.or(index), extra_index: self.extra_index.into_iter().chain(extra_index).collect(), flat_index: self.flat_index.into_iter().chain(flat_index).collect(), @@ -351,7 +365,8 @@ impl IndexLocations { /// Returns `true` if no index configuration is set, i.e., the [`IndexLocations`] matches the /// default configuration. pub fn is_none(&self) -> bool { - self.index.is_none() + self.sources.is_empty() + && self.index.is_none() && self.extra_index.is_empty() && self.flat_index.is_empty() && !self.no_index @@ -402,6 +417,7 @@ impl<'a> IndexLocations { /// Clone the index locations into a [`IndexUrls`] instance. pub fn index_urls(&'a self) -> IndexUrls { IndexUrls { + sources: self.sources.clone(), index: self.index.clone(), extra_index: self.extra_index.clone(), no_index: self.no_index, @@ -424,6 +440,7 @@ impl<'a> IndexLocations { /// From a pip perspective, this type merges `--index-url` and `--extra-index-url`. #[derive(Debug, Clone)] pub struct IndexUrls { + sources: Vec, index: Option, extra_index: Vec, no_index: bool, @@ -433,6 +450,7 @@ impl Default for IndexUrls { /// By default, use the `PyPI` index. fn default() -> Self { Self { + sources: Vec::new(), index: Some(DEFAULT_INDEX_URL.clone()), extra_index: Vec::new(), no_index: false, @@ -441,6 +459,15 @@ impl Default for IndexUrls { } impl<'a> IndexUrls { + /// Return an iterator over the `tool.uv.index` sources. + fn sources(&'a self) -> impl Iterator + 'a { + if self.no_index { + Either::Left(std::iter::empty()) + } else { + Either::Right(self.sources.iter().map(|source| &source.index)) + } + } + /// Return the fallback [`IndexUrl`] entry. /// /// If `--no-index` is set, return `None`. @@ -473,13 +500,14 @@ impl<'a> IndexUrls { /// If `no_index` was enabled, then this always returns an empty /// iterator. pub fn indexes(&'a self) -> impl Iterator + 'a { - self.extra_index().chain(self.index()) + self.sources().chain(self.extra_index()).chain(self.index()) } } impl From for IndexUrls { fn from(locations: IndexLocations) -> Self { Self { + sources: locations.sources, index: locations.index, extra_index: locations.extra_index, no_index: locations.no_index, diff --git a/crates/distribution-types/src/named_index.rs b/crates/distribution-types/src/named_index.rs index 4e9a8a2966af..15d6870aa9b5 100644 --- a/crates/distribution-types/src/named_index.rs +++ b/crates/distribution-types/src/named_index.rs @@ -1,10 +1,10 @@ -use url::Url; +use crate::IndexUrl; #[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct IndexSource { pub name: String, - pub index: Url, + pub index: IndexUrl, #[serde(default)] pub kind: IndexKind, } diff --git a/crates/pep508-rs/src/verbatim_url.rs b/crates/pep508-rs/src/verbatim_url.rs index 9445b13cfd78..d48b979515ab 100644 --- a/crates/pep508-rs/src/verbatim_url.rs +++ b/crates/pep508-rs/src/verbatim_url.rs @@ -134,6 +134,11 @@ impl VerbatimUrl { self.url.clone() } + /// Convert a [`VerbatimUrl`] into a [`Url`]. + pub fn into_url(self) -> Url { + self.url + } + /// Return the underlying [`Path`], if the URL is a file URL. pub fn as_path(&self) -> Result { self.url diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index dc66217124c0..ffe15f7a3c50 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -242,6 +242,7 @@ pub fn resolver_options( } = build_args; ResolverOptions { + index: None, index_url: index_args.index_url.and_then(Maybe::into_option), extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { extra_index_urls @@ -325,6 +326,7 @@ pub fn resolver_installer_options( } = build_args; ResolverInstallerOptions { + index: None, index_url: index_args.index_url.and_then(Maybe::into_option), extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { extra_index_urls diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index 8a0ee0ed93fa..2f8efad3396b 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -126,7 +126,7 @@ impl LoweredRequirement { else { return Err(LoweringError::MissingIndex(requirement.name, index)); }; - registry_source(&requirement, index)? + registry_source(&requirement, index.into_url())? } Source::Workspace { workspace: is_workspace, @@ -245,7 +245,7 @@ impl LoweredRequirement { else { return Err(LoweringError::MissingIndex(requirement.name, index)); }; - registry_source(&requirement, index)? + registry_source(&requirement, index.into_url())? } Source::Workspace { .. } => { return Err(LoweringError::WorkspaceMember); diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 565b7b119788..8aca1d80b3c9 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf}; use serde::{Deserialize, Serialize}; -use distribution_types::{FlatIndexLocation, IndexUrl}; +use distribution_types::{FlatIndexLocation, IndexSource, IndexUrl}; use install_wheel_rs::linker::LinkMode; use pep508_rs::Requirement; use pypi_types::{SupportedEnvironments, VerbatimParsedUrl}; @@ -252,6 +252,7 @@ pub struct GlobalOptions { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct InstallerOptions { + pub index: Option>, pub index_url: Option, pub extra_index_url: Option>, pub no_index: Option, @@ -279,6 +280,7 @@ pub struct InstallerOptions { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ResolverOptions { + pub index: Option>, pub index_url: Option, pub extra_index_url: Option>, pub no_index: Option, @@ -311,6 +313,21 @@ pub struct ResolverOptions { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ResolverInstallerOptions { + /// The URL of the Python package index (by default: ). + /// + /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) + /// (the simple repository API), or a local directory laid out in the same format. + /// + /// The index provided by this setting is given lower priority than any indexes specified via + /// [`extra_index_url`](#extra-index-url). + #[option( + default = "\"https://pypi.org/simple\"", + value_type = "str", + example = r#" + index-url = "https://test.pypi.org/simple" + "# + )] + pub index: Option>, /// The URL of the Python package index (by default: ). /// /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) @@ -1281,6 +1298,7 @@ pub struct PipOptions { impl From for ResolverOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1309,6 +1327,7 @@ impl From for ResolverOptions { impl From for InstallerOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1342,6 +1361,7 @@ impl From for InstallerOptions { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ToolOptions { + pub index: Option>, pub index_url: Option, pub extra_index_url: Option>, pub no_index: Option, @@ -1367,6 +1387,7 @@ pub struct ToolOptions { impl From for ToolOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1394,6 +1415,7 @@ impl From for ToolOptions { impl From for ResolverInstallerOptions { fn from(value: ToolOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index bef32ae03ebe..68e0c555cfec 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -273,8 +273,13 @@ pub(crate) async fn pip_compile( let dev = Vec::default(); // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + Vec::default(), + index_url, + extra_index_urls, + find_links, + no_index, + ); // Add all authenticated sources to the cache. for url in index_locations.urls() { diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 760b48b01835..3cb9a51d7c72 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -270,8 +270,13 @@ pub(crate) async fn pip_install( let dev = Vec::default(); // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + Vec::default(), + index_url, + extra_index_urls, + find_links, + no_index, + ); // Add all authenticated sources to the cache. for url in index_locations.urls() { diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index aec9433e2652..58f2fb0a11a4 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -214,8 +214,13 @@ pub(crate) async fn pip_sync( }; // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + Vec::default(), + index_url, + extra_index_urls, + find_links, + no_index, + ); // Add all authenticated sources to the cache. for url in index_locations.urls() { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index ae06f015c72e..bac2f3aac9ea 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1873,6 +1873,7 @@ impl From for ResolverSettings { fn from(value: ResolverOptions) -> Self { Self { index_locations: IndexLocations::new( + value.index.unwrap_or_default(), value.index_url, value.extra_index_url.unwrap_or_default(), value.find_links.unwrap_or_default(), @@ -1995,6 +1996,7 @@ impl From for ResolverInstallerSettings { fn from(value: ResolverInstallerOptions) -> Self { Self { index_locations: IndexLocations::new( + value.index.unwrap_or_default(), value.index_url, value.extra_index_url.unwrap_or_default(), value.find_links.unwrap_or_default(), @@ -2148,6 +2150,7 @@ impl PipSettings { } = pip.unwrap_or_default(); let ResolverInstallerOptions { + index: top_level_index, index_url: top_level_index_url, extra_index_url: top_level_extra_index_url, no_index: top_level_no_index, @@ -2202,6 +2205,7 @@ impl PipSettings { Self { index_locations: IndexLocations::new( + top_level_index.unwrap_or_default(), args.index_url.combine(index_url), args.extra_index_url .combine(extra_index_url)