Respect named `--index` and `--default-index` values in `tool.uv.sources` (#7910)

## Summary

If you pass a named index via the CLI, you can now reference it as a
named source. This required some surprisingly large refactors, since we
now need to be able to track whether a given index was provided on the
CLI vs. elsewhere (since, e.g., we don't want users to be able to
reference named indexes defined in global configuration).

Closes https://github.com/astral-sh/uv/issues/7899.
This commit is contained in:
Charlie Marsh 2024-10-15 16:56:24 -07:00 committed by GitHub
parent a034a8b83b
commit 2153c6ac0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 370 additions and 133 deletions

View File

@ -30,7 +30,7 @@ use tracing::{debug, info_span, instrument, Instrument};
pub use crate::error::{Error, MissingHeaderCause};
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
use uv_distribution::{LowerBound, RequiresDist};
use uv_distribution_types::Resolution;
use uv_distribution_types::{IndexLocations, Resolution};
use uv_fs::{rename_with_retry, PythonExt, Simplified};
use uv_pep440::Version;
use uv_pep508::PackageName;
@ -250,6 +250,7 @@ impl SourceBuild {
build_context: &impl BuildContext,
source_build_context: SourceBuildContext,
version_id: Option<String>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
config_settings: ConfigSettings,
build_isolation: BuildIsolation<'_>,
@ -272,6 +273,7 @@ impl SourceBuild {
let (pep517_backend, project) = Self::extract_pep517_backend(
&source_tree,
fallback_package_name,
locations,
source_strategy,
&default_backend,
)
@ -371,6 +373,7 @@ impl SourceBuild {
package_name.as_ref(),
package_version.as_ref(),
version_id.as_deref(),
locations,
source_strategy,
build_kind,
level,
@ -433,6 +436,7 @@ impl SourceBuild {
async fn extract_pep517_backend(
source_tree: &Path,
package_name: Option<&PackageName>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
default_backend: &Pep517Backend,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
@ -465,6 +469,7 @@ impl SourceBuild {
let requires_dist = RequiresDist::from_project_maybe_workspace(
requires_dist,
source_tree,
locations,
source_strategy,
LowerBound::Allow,
)
@ -803,6 +808,7 @@ async fn create_pep517_build_environment(
package_name: Option<&PackageName>,
package_version: Option<&Version>,
version_id: Option<&str>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
build_kind: BuildKind,
level: BuildOutput,
@ -915,6 +921,7 @@ async fn create_pep517_build_environment(
let requires_dist = RequiresDist::from_project_maybe_workspace(
requires_dist,
source_tree,
locations,
source_strategy,
LowerBound::Allow,
)

View File

@ -14,7 +14,7 @@ use uv_configuration::{
ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
};
use uv_distribution_types::{Index, IndexUrl};
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
use uv_normalize::{ExtraName, PackageName};
use uv_pep508::Requirement;
use uv_pypi_types::VerbatimParsedUrl;
@ -774,26 +774,66 @@ impl<T> Maybe<T> {
}
}
/// Parse a string into an [`IndexUrl`], mapping the empty string to `None`.
fn parse_index_url(input: &str) -> Result<Maybe<IndexUrl>, String> {
/// Parse an `--index-url` argument into an [`PipIndex`], mapping the empty string to `None`.
fn parse_index_url(input: &str) -> Result<Maybe<PipIndex>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match IndexUrl::from_str(input) {
Ok(url) => Ok(Maybe::Some(url)),
Err(err) => Err(err.to_string()),
}
IndexUrl::from_str(input)
.map(Index::from_index_url)
.map(|index| Index {
origin: Some(Origin::Cli),
..index
})
.map(PipIndex::from)
.map(Maybe::Some)
.map_err(|err| err.to_string())
}
}
/// Parse a string into an [`Index`], mapping the empty string to `None`.
fn parse_index_source(input: &str) -> Result<Maybe<Index>, String> {
/// Parse an `--extra-index-url` argument into an [`PipExtraIndex`], mapping the empty string to `None`.
fn parse_extra_index_url(input: &str) -> Result<Maybe<PipExtraIndex>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
IndexUrl::from_str(input)
.map(Index::from_extra_index_url)
.map(|index| Index {
origin: Some(Origin::Cli),
..index
})
.map(PipExtraIndex::from)
.map(Maybe::Some)
.map_err(|err| err.to_string())
}
}
/// Parse a `--find-links` argument into an [`PipFindLinks`], mapping the empty string to `None`.
fn parse_find_links(input: &str) -> Result<Maybe<PipFindLinks>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
IndexUrl::from_str(input)
.map(Index::from_find_links)
.map(|index| Index {
origin: Some(Origin::Cli),
..index
})
.map(PipFindLinks::from)
.map(Maybe::Some)
.map_err(|err| err.to_string())
}
}
/// Parse an `--index` argument into an [`Index`], mapping the empty string to `None`.
fn parse_index(input: &str) -> Result<Maybe<Index>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match Index::from_str(input) {
Ok(index) => Ok(Maybe::Some(Index {
default: false,
origin: Some(Origin::Cli),
..index
})),
Err(err) => Err(err.to_string()),
@ -801,14 +841,15 @@ fn parse_index_source(input: &str) -> Result<Maybe<Index>, String> {
}
}
/// Parse a string into an [`Index`], mapping the empty string to `None`.
fn parse_default_index_source(input: &str) -> Result<Maybe<Index>, String> {
/// Parse a `--default-index` argument into an [`Index`], mapping the empty string to `None`.
fn parse_default_index(input: &str) -> Result<Maybe<Index>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match Index::from_str(input) {
Ok(index) => Ok(Maybe::Some(Index {
default: true,
origin: Some(Origin::Cli),
..index
})),
Err(err) => Err(err.to_string()),
@ -3839,7 +3880,7 @@ pub struct IndexArgs {
/// All indexes provided via this flag take priority over the index specified by
/// `--default-index` (which defaults to PyPI). When multiple `--index` flags are
/// provided, earlier values take priority.
#[arg(long, env = "UV_INDEX", value_delimiter = ' ', value_parser = parse_index_source, help_heading = "Index options")]
#[arg(long, env = "UV_INDEX", value_delimiter = ' ', value_parser = parse_index, help_heading = "Index options")]
pub index: Option<Vec<Maybe<Index>>>,
/// The URL of the default package index (by default: <https://pypi.org/simple>).
@ -3849,7 +3890,7 @@ pub struct IndexArgs {
///
/// The index given by this flag is given lower priority than all other indexes specified via
/// the `--index` flag.
#[arg(long, env = "UV_DEFAULT_INDEX", value_parser = parse_default_index_source, help_heading = "Index options")]
#[arg(long, env = "UV_DEFAULT_INDEX", value_parser = parse_default_index, help_heading = "Index options")]
pub default_index: Option<Maybe<Index>>,
/// (Deprecated: use `--default-index` instead) The URL of the Python package index (by default: <https://pypi.org/simple>).
@ -3860,7 +3901,7 @@ pub struct IndexArgs {
/// The index given by this flag is given lower priority than all other
/// indexes specified via the `--extra-index-url` flag.
#[arg(long, short, env = EnvVars::UV_INDEX_URL, value_parser = parse_index_url, help_heading = "Index options")]
pub index_url: Option<Maybe<IndexUrl>>,
pub index_url: Option<Maybe<PipIndex>>,
/// (Deprecated: use `--index` instead) Extra URLs of package indexes to use, in addition to `--index-url`.
///
@ -3870,8 +3911,8 @@ pub struct IndexArgs {
/// All indexes provided via this flag take priority over the index specified by
/// `--index-url` (which defaults to PyPI). When multiple `--extra-index-url` flags are
/// provided, earlier values take priority.
#[arg(long, env = EnvVars::UV_EXTRA_INDEX_URL, value_delimiter = ' ', value_parser = parse_index_url, help_heading = "Index options")]
pub extra_index_url: Option<Vec<Maybe<IndexUrl>>>,
#[arg(long, env = EnvVars::UV_EXTRA_INDEX_URL, value_delimiter = ' ', value_parser = parse_extra_index_url, help_heading = "Index options")]
pub extra_index_url: Option<Vec<Maybe<PipExtraIndex>>>,
/// Locations to search for candidate distributions, in addition to those found in the registry
/// indexes.
@ -3885,10 +3926,10 @@ pub struct IndexArgs {
long,
short,
env = EnvVars::UV_FIND_LINKS,
value_parser = parse_index_url,
value_parser = parse_find_links,
help_heading = "Index options"
)]
pub find_links: Option<Vec<Maybe<IndexUrl>>>,
pub find_links: Option<Vec<Maybe<PipFindLinks>>>,
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
/// provided via `--find-links`.

View File

@ -1,5 +1,6 @@
use uv_cache::Refresh;
use uv_configuration::ConfigSettings;
use uv_distribution_types::{PipExtraIndex, PipFindLinks, PipIndex};
use uv_resolver::PrereleaseMode;
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
@ -201,11 +202,12 @@ impl From<IndexArgs> for PipOptions {
.combine(
index.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()),
),
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_url| {
extra_index_url
index_url: index_url.and_then(Maybe::into_option).map(PipIndex::from),
extra_index_url: extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.map(PipExtraIndex::from)
.collect()
}),
no_index: if no_index { Some(true) } else { None },
@ -213,6 +215,7 @@ impl From<IndexArgs> for PipOptions {
find_links
.into_iter()
.filter_map(Maybe::into_option)
.map(PipFindLinks::from)
.collect()
}),
..PipOptions::default()

View File

@ -156,7 +156,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
self.sources
}
fn index_locations(&self) -> &IndexLocations {
fn locations(&self) -> &IndexLocations {
self.index_locations
}
@ -350,6 +350,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
self,
self.source_build_context.clone(),
version_id,
self.index_locations,
sources,
self.config_settings.clone(),
self.build_isolation,

View File

@ -1,9 +1,11 @@
use crate::{IndexUrl, IndexUrlError};
use std::str::FromStr;
use thiserror::Error;
use url::Url;
use uv_auth::Credentials;
use crate::origin::Origin;
use crate::{IndexUrl, IndexUrlError};
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Index {
@ -52,6 +54,9 @@ pub struct Index {
/// is given the highest priority when resolving packages.
#[serde(default)]
pub default: bool,
/// The origin of the index (e.g., a CLI flag, a user-level configuration file, etc.).
#[serde(skip)]
pub origin: Option<Origin>,
// /// The type of the index.
// ///
// /// Indexes can either be PEP 503-compliant (i.e., a registry implementing the Simple API) or
@ -81,6 +86,7 @@ impl Index {
name: None,
explicit: false,
default: true,
origin: None,
}
}
@ -91,6 +97,7 @@ impl Index {
name: None,
explicit: false,
default: false,
origin: None,
}
}
@ -101,14 +108,27 @@ impl Index {
name: None,
explicit: false,
default: false,
origin: None,
}
}
/// Set the [`Origin`] of the index.
#[must_use]
pub fn with_origin(mut self, origin: Origin) -> Self {
self.origin = Some(origin);
self
}
/// Return the [`IndexUrl`] of the index.
pub fn url(&self) -> &IndexUrl {
&self.url
}
/// Consume the [`Index`] and return the [`IndexUrl`].
pub fn into_url(self) -> IndexUrl {
self.url
}
/// Return the raw [`URL`] of the index.
pub fn raw_url(&self) -> &Url {
self.url.url()
@ -145,6 +165,7 @@ impl FromStr for Index {
url,
explicit: false,
default: false,
origin: None,
});
}
}
@ -156,6 +177,7 @@ impl FromStr for Index {
url,
explicit: false,
default: false,
origin: None,
})
}
}

View File

@ -60,6 +60,8 @@ pub use crate::id::*;
pub use crate::index::*;
pub use crate::index_url::*;
pub use crate::installed::*;
pub use crate::origin::*;
pub use crate::pip_index::*;
pub use crate::prioritized_distribution::*;
pub use crate::resolution::*;
pub use crate::resolved::*;
@ -79,6 +81,8 @@ mod id;
mod index;
mod index_url;
mod installed;
mod origin;
mod pip_index;
mod prioritized_distribution;
mod resolution;
mod resolved;

View File

@ -0,0 +1,12 @@
/// The origin of a piece of configuration.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub enum Origin {
/// The setting was provided via the CLI.
Cli,
/// The setting was provided via a user-level configuration file.
User,
/// The setting was provided via a project-level configuration file.
Project,
/// The setting was provided via a `requirements.txt` file.
RequirementsTxt,
}

View File

@ -0,0 +1,59 @@
//! Compatibility structs for converting between [`IndexUrl`] and [`Index`]. These structs are
//! parsed and deserialized as [`IndexUrl`], but are stored as [`Index`] with the appropriate
//! flags set.
use serde::{Deserialize, Deserializer, Serialize};
use crate::{Index, IndexUrl};
macro_rules! impl_index {
($name:ident, $from:expr) => {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct $name(Index);
impl From<$name> for Index {
fn from(value: $name) -> Self {
value.0
}
}
impl From<Index> for $name {
fn from(value: Index) -> Self {
Self(value)
}
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.url().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<$name, D::Error>
where
D: Deserializer<'de>,
{
IndexUrl::deserialize(deserializer).map($from).map(Self)
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for $name {
fn schema_name() -> String {
IndexUrl::schema_name()
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
IndexUrl::json_schema(gen)
}
}
};
}
impl_index!(PipIndex, Index::from_index_url);
impl_index!(PipExtraIndex, Index::from_extra_index_url);
impl_index!(PipFindLinks, Index::from_find_links);

View File

@ -6,7 +6,7 @@ use thiserror::Error;
use url::Url;
use uv_distribution_filename::DistExtension;
use uv_distribution_types::Index;
use uv_distribution_types::{Index, IndexLocations, Origin};
use uv_git::GitReference;
use uv_normalize::PackageName;
use uv_pep440::VersionSpecifiers;
@ -20,7 +20,7 @@ use uv_workspace::Workspace;
pub struct LoweredRequirement(Requirement);
#[derive(Debug, Clone, Copy)]
enum Origin {
enum RequirementOrigin {
/// The `tool.uv.sources` were read from the project.
Project,
/// The `tool.uv.sources` were read from the workspace root.
@ -35,15 +35,16 @@ impl LoweredRequirement {
project_dir: &'data Path,
project_sources: &'data BTreeMap<PackageName, Sources>,
project_indexes: &'data [Index],
locations: &'data IndexLocations,
workspace: &'data Workspace,
lower_bound: LowerBound,
) -> impl Iterator<Item = Result<LoweredRequirement, LoweringError>> + 'data {
) -> impl Iterator<Item = Result<Self, LoweringError>> + 'data {
let (source, origin) = if let Some(source) = project_sources.get(&requirement.name) {
(Some(source), Origin::Project)
(Some(source), RequirementOrigin::Project)
} else if let Some(source) = workspace.sources().get(&requirement.name) {
(Some(source), Origin::Workspace)
(Some(source), RequirementOrigin::Workspace)
} else {
(None, Origin::Project)
(None, RequirementOrigin::Project)
};
let source = source.cloned();
@ -155,16 +156,14 @@ impl LoweredRequirement {
Source::Registry { index, marker } => {
// Identify the named index from either the project indexes or the workspace indexes,
// in that order.
let Some(index) = project_indexes
.iter()
let Some(index) = locations
.indexes()
.filter(|index| matches!(index.origin, Some(Origin::Cli)))
.chain(project_indexes.iter())
.chain(workspace.indexes().iter())
.find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
.or_else(|| {
workspace.indexes().iter().find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
})
.map(|Index { url: index, .. }| index.clone())
else {
return Err(LoweringError::MissingIndex(
@ -260,7 +259,8 @@ impl LoweredRequirement {
dir: &'data Path,
sources: &'data BTreeMap<PackageName, Sources>,
indexes: &'data [Index],
) -> impl Iterator<Item = Result<LoweredRequirement, LoweringError>> + 'data {
locations: &'data IndexLocations,
) -> impl Iterator<Item = Result<Self, LoweringError>> + 'data {
let source = sources.get(&requirement.name).cloned();
let Some(source) = source else {
@ -332,7 +332,7 @@ impl LoweredRequirement {
}
let source = path_source(
PathBuf::from(path),
Origin::Project,
RequirementOrigin::Project,
dir,
dir,
editable.unwrap_or(false),
@ -340,8 +340,10 @@ impl LoweredRequirement {
(source, marker)
}
Source::Registry { index, marker } => {
let Some(index) = indexes
.iter()
let Some(index) = locations
.indexes()
.filter(|index| matches!(index.origin, Some(Origin::Cli)))
.chain(indexes.iter())
.find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
@ -508,15 +510,15 @@ fn registry_source(
/// Convert a path string to a file or directory source.
fn path_source(
path: impl AsRef<Path>,
origin: Origin,
origin: RequirementOrigin,
project_dir: &Path,
workspace_root: &Path,
editable: bool,
) -> Result<RequirementSource, LoweringError> {
let path = path.as_ref();
let base = match origin {
Origin::Project => project_dir,
Origin::Workspace => workspace_root,
RequirementOrigin::Project => project_dir,
RequirementOrigin::Workspace => workspace_root,
};
let url = VerbatimUrl::from_path(path, base)?.with_given(path.to_string_lossy());
let install_path = url.to_file_path().map_err(|()| {

View File

@ -4,6 +4,7 @@ use std::path::Path;
use thiserror::Error;
use uv_configuration::SourceStrategy;
use uv_distribution_types::IndexLocations;
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pypi_types::{HashDigest, ResolutionMetadata};
@ -61,6 +62,7 @@ impl Metadata {
pub async fn from_workspace(
metadata: ResolutionMetadata,
install_path: &Path,
locations: &IndexLocations,
sources: SourceStrategy,
) -> Result<Self, MetadataError> {
// Lower the requirements.
@ -76,6 +78,7 @@ impl Metadata {
provides_extras: metadata.provides_extras,
},
install_path,
locations,
sources,
LowerBound::Warn,
)

View File

@ -1,10 +1,11 @@
use crate::metadata::lowering::LowerBound;
use crate::metadata::{LoweredRequirement, MetadataError};
use crate::Metadata;
use crate::metadata::lowering::LowerBound;
use std::collections::BTreeMap;
use std::path::Path;
use uv_configuration::SourceStrategy;
use uv_distribution_types::IndexLocations;
use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
use uv_workspace::pyproject::ToolUvSources;
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
@ -38,6 +39,7 @@ impl RequiresDist {
pub async fn from_project_maybe_workspace(
metadata: uv_pypi_types::RequiresDist,
install_path: &Path,
locations: &IndexLocations,
sources: SourceStrategy,
lower_bound: LowerBound,
) -> Result<Self, MetadataError> {
@ -50,18 +52,25 @@ impl RequiresDist {
return Ok(Self::from_metadata23(metadata));
};
Self::from_project_workspace(metadata, &project_workspace, sources, lower_bound)
Self::from_project_workspace(
metadata,
&project_workspace,
locations,
sources,
lower_bound,
)
}
fn from_project_workspace(
metadata: uv_pypi_types::RequiresDist,
project_workspace: &ProjectWorkspace,
locations: &IndexLocations,
source_strategy: SourceStrategy,
lower_bound: LowerBound,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
let indexes = match source_strategy {
let project_indexes = match source_strategy {
SourceStrategy::Enabled => project_workspace
.current_project()
.pyproject_toml()
@ -75,7 +84,7 @@ impl RequiresDist {
// Collect any `tool.uv.sources` and `tool.uv.dev_dependencies` from `pyproject.toml`.
let empty = BTreeMap::default();
let sources = match source_strategy {
let project_sources = match source_strategy {
SourceStrategy::Enabled => project_workspace
.current_project()
.pyproject_toml()
@ -107,8 +116,9 @@ impl RequiresDist {
requirement,
&metadata.name,
project_workspace.project_root(),
sources,
indexes,
project_sources,
project_indexes,
locations,
project_workspace.workspace(),
lower_bound,
)
@ -141,8 +151,9 @@ impl RequiresDist {
requirement,
&metadata.name,
project_workspace.project_root(),
sources,
indexes,
project_sources,
project_indexes,
locations,
project_workspace.workspace(),
lower_bound,
)
@ -188,6 +199,7 @@ mod test {
use indoc::indoc;
use insta::assert_snapshot;
use uv_configuration::SourceStrategy;
use uv_distribution_types::IndexLocations;
use uv_workspace::pyproject::PyProjectToml;
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
@ -214,7 +226,8 @@ mod test {
Ok(RequiresDist::from_project_workspace(
requires_dist,
&project_workspace,
SourceStrategy::Enabled,
&IndexLocations::default(),
SourceStrategy::default(),
LowerBound::Warn,
)?)
}

View File

@ -388,6 +388,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let requires_dist = RequiresDist::from_project_maybe_workspace(
requires_dist,
project_root,
self.build_context.locations(),
self.build_context.sources(),
LowerBound::Warn,
)
@ -1086,6 +1087,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Metadata::from_workspace(
metadata,
resource.install_path.as_ref(),
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
@ -1120,6 +1122,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Metadata::from_workspace(
metadata,
resource.install_path.as_ref(),
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
@ -1149,6 +1152,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Metadata::from_workspace(
metadata,
resource.install_path.as_ref(),
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
@ -1194,6 +1198,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Metadata::from_workspace(
metadata,
resource.install_path.as_ref(),
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
@ -1374,7 +1379,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Self::read_static_metadata(source, fetch.path(), resource.subdirectory).await?
{
return Ok(ArchiveMetadata::from(
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
Metadata::from_workspace(
metadata,
&path,
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
));
}
@ -1395,7 +1406,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
debug!("Using cached metadata for: {source}");
return Ok(ArchiveMetadata::from(
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
Metadata::from_workspace(
metadata,
&path,
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
));
}
}
@ -1420,7 +1437,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.map_err(Error::CacheWrite)?;
return Ok(ArchiveMetadata::from(
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
Metadata::from_workspace(
metadata,
&path,
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
));
}
@ -1460,7 +1483,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.map_err(Error::CacheWrite)?;
Ok(ArchiveMetadata::from(
Metadata::from_workspace(metadata, fetch.path(), self.build_context.sources()).await?,
Metadata::from_workspace(
metadata,
fetch.path(),
self.build_context.locations(),
self.build_context.sources(),
)
.await?,
))
}

View File

@ -177,7 +177,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
index,
build_context.git(),
build_context.capabilities(),
build_context.index_locations(),
build_context.locations(),
provider,
installed_packages,
)

View File

@ -5,7 +5,7 @@ use url::Url;
use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple, TrustedPublishing,
};
use uv_distribution_types::IndexUrl;
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::linker::LinkMode;
use uv_pypi_types::SupportedEnvironments;
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
@ -72,13 +72,16 @@ macro_rules! impl_combine_or {
impl_combine_or!(AnnotationStyle);
impl_combine_or!(ExcludeNewer);
impl_combine_or!(Index);
impl_combine_or!(IndexStrategy);
impl_combine_or!(IndexUrl);
impl_combine_or!(Url);
impl_combine_or!(KeyringProviderType);
impl_combine_or!(LinkMode);
impl_combine_or!(NonZeroUsize);
impl_combine_or!(PathBuf);
impl_combine_or!(PipExtraIndex);
impl_combine_or!(PipFindLinks);
impl_combine_or!(PipIndex);
impl_combine_or!(PrereleaseMode);
impl_combine_or!(PythonDownloads);
impl_combine_or!(PythonPreference);
@ -88,6 +91,7 @@ impl_combine_or!(String);
impl_combine_or!(SupportedEnvironments);
impl_combine_or!(TargetTriple);
impl_combine_or!(TrustedPublishing);
impl_combine_or!(Url);
impl_combine_or!(bool);
impl<T> Combine for Option<Vec<T>> {

View File

@ -1,13 +1,12 @@
use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
use url::Url;
use uv_cache_info::CacheKey;
use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
TrustedHost, TrustedPublishing,
};
use uv_distribution_types::{Index, IndexUrl, StaticMetadata};
use uv_distribution_types::{Index, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata};
use uv_install_wheel::linker::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName};
@ -231,10 +230,10 @@ pub struct GlobalOptions {
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct InstallerOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub index_url: Option<PipIndex>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
pub no_index: Option<bool>,
pub find_links: Option<Vec<IndexUrl>>,
pub find_links: Option<Vec<PipFindLinks>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
@ -256,10 +255,10 @@ pub struct InstallerOptions {
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct ResolverOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub index_url: Option<PipIndex>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
pub no_index: Option<bool>,
pub find_links: Option<Vec<IndexUrl>>,
pub find_links: Option<Vec<PipFindLinks>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
@ -339,7 +338,7 @@ pub struct ResolverInstallerOptions {
index-url = "https://test.pypi.org/simple"
"#
)]
pub index_url: Option<IndexUrl>,
pub index_url: Option<PipIndex>,
/// Extra URLs of package indexes to use, in addition to `--index-url`.
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
@ -360,7 +359,7 @@ pub struct ResolverInstallerOptions {
extra-index-url = ["https://download.pytorch.org/whl/cpu"]
"#
)]
pub extra_index_url: Option<Vec<IndexUrl>>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
/// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and
/// those provided via `--find-links`.
#[option(
@ -386,7 +385,7 @@ pub struct ResolverInstallerOptions {
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
"#
)]
pub find_links: Option<Vec<IndexUrl>>,
pub find_links: Option<Vec<PipFindLinks>>,
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, uv will stop at the first index on which a given package is available, and
@ -754,7 +753,7 @@ pub struct PipOptions {
index-url = "https://test.pypi.org/simple"
"#
)]
pub index_url: Option<IndexUrl>,
pub index_url: Option<PipIndex>,
/// Extra URLs of package indexes to use, in addition to `--index-url`.
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
@ -772,7 +771,7 @@ pub struct PipOptions {
extra-index-url = ["https://download.pytorch.org/whl/cpu"]
"#
)]
pub extra_index_url: Option<Vec<IndexUrl>>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
/// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and
/// those provided via `--find-links`.
#[option(
@ -798,7 +797,7 @@ pub struct PipOptions {
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
"#
)]
pub find_links: Option<Vec<IndexUrl>>,
pub find_links: Option<Vec<PipFindLinks>>,
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, uv will stop at the first index on which a given package is available, and
@ -1411,10 +1410,10 @@ impl From<ResolverInstallerOptions> for InstallerOptions {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ToolOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub index_url: Option<PipIndex>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
pub no_index: Option<bool>,
pub find_links: Option<Vec<IndexUrl>>,
pub find_links: Option<Vec<PipFindLinks>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
@ -1502,7 +1501,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct OptionsWire {
// #[serde(flatten)]
// globals: GlobalOptions,
// globals: GlobalOptions
native_tls: Option<bool>,
offline: Option<bool>,
no_cache: Option<bool>,
@ -1515,12 +1514,12 @@ pub struct OptionsWire {
concurrent_installs: Option<NonZeroUsize>,
// #[serde(flatten)]
// top_level: ResolverInstallerOptions,
// top_level: ResolverInstallerOptions
index: Option<Vec<Index>>,
index_url: Option<IndexUrl>,
extra_index_url: Option<Vec<IndexUrl>>,
index_url: Option<PipIndex>,
extra_index_url: Option<Vec<PipExtraIndex>>,
no_index: Option<bool>,
find_links: Option<Vec<IndexUrl>>,
find_links: Option<Vec<PipFindLinks>>,
index_strategy: Option<IndexStrategy>,
keyring_provider: Option<KeyringProviderType>,
allow_insecure_host: Option<Vec<TrustedHost>>,
@ -1542,6 +1541,9 @@ pub struct OptionsWire {
no_build_package: Option<Vec<PackageName>>,
no_binary: Option<bool>,
no_binary_package: Option<Vec<PackageName>>,
// #[serde(flatten)]
// publish: PublishOptions
publish_url: Option<Url>,
trusted_publishing: Option<TrustedPublishing>,

View File

@ -79,7 +79,7 @@ pub trait BuildContext {
fn sources(&self) -> SourceStrategy;
/// The index locations being searched.
fn index_locations(&self) -> &IndexLocations;
fn locations(&self) -> &IndexLocations;
/// Resolve the given requirements into a ready-to-install set of package versions.
fn resolve<'a>(

View File

@ -16,7 +16,7 @@ use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DependencyMetadata, Index, IndexCapabilities, IndexLocations, NameRequirementSpecification,
UnresolvedRequirementSpecification, Verbatim,
Origin, UnresolvedRequirementSpecification, Verbatim,
};
use uv_fs::Simplified;
use uv_git::GitResolver;
@ -277,8 +277,13 @@ pub(crate) async fn pip_compile(
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links
.into_iter()
.map(Index::from_find_links)
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links.into_iter().map(Index::from_find_links).collect(),
no_index,
);

View File

@ -13,7 +13,7 @@ use uv_configuration::{
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Resolution,
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin, Resolution,
UnresolvedRequirementSpecification,
};
use uv_fs::Simplified;
@ -279,8 +279,13 @@ pub(crate) async fn pip_install(
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links
.into_iter()
.map(Index::from_find_links)
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links.into_iter().map(Index::from_find_links).collect(),
no_index,
);

View File

@ -12,7 +12,7 @@ use uv_configuration::{
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Resolution};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, Resolution};
use uv_fs::Simplified;
use uv_install_wheel::linker::LinkMode;
use uv_installer::SitePackages;
@ -215,8 +215,13 @@ pub(crate) async fn pip_sync(
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links
.into_iter()
.map(Index::from_find_links)
.map(|index| index.with_origin(Origin::RequirementsTxt))
.collect(),
find_links.into_iter().map(Index::from_find_links).collect(),
no_index,
);

View File

@ -247,6 +247,7 @@ pub(crate) async fn run(
script_dir.as_ref(),
script_sources,
script_indexes,
&settings.index_locations,
)
.map_ok(LoweredRequirement::into_inner)
})

View File

@ -1972,20 +1972,14 @@ impl From<ResolverOptions> for ResolverSettings {
.index
.into_iter()
.flatten()
.chain(
value
.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(value.index_url.into_iter().map(Index::from_index_url))
.chain(value.extra_index_url.into_iter().flatten().map(Index::from))
.chain(value.index_url.into_iter().map(Index::from))
.collect(),
value
.find_links
.into_iter()
.flatten()
.map(Index::from_find_links)
.map(Index::from)
.collect(),
value.no_index.unwrap_or_default(),
),
@ -2115,20 +2109,14 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
.index
.into_iter()
.flatten()
.chain(
value
.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(value.index_url.into_iter().map(Index::from_index_url))
.chain(value.extra_index_url.into_iter().flatten().map(Index::from))
.chain(value.index_url.into_iter().map(Index::from))
.collect(),
value
.find_links
.into_iter()
.flatten()
.map(Index::from_find_links)
.map(Index::from)
.collect(),
value.no_index.unwrap_or_default(),
),
@ -2319,9 +2307,9 @@ impl PipSettings {
//
// For example, prefer `tool.uv.pip.index-url` over `tool.uv.index-url`.
let index = index.combine(top_level_index);
let no_index = no_index.combine(top_level_no_index);
let index_url = index_url.combine(top_level_index_url);
let extra_index_url = extra_index_url.combine(top_level_extra_index_url);
let no_index = no_index.combine(top_level_no_index);
let find_links = find_links.combine(top_level_find_links);
let index_strategy = index_strategy.combine(top_level_index_strategy);
let keyring_provider = keyring_provider.combine(top_level_keyring_provider);
@ -2347,27 +2335,17 @@ impl PipSettings {
args.index
.into_iter()
.flatten()
.chain(
args.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(args.index_url.into_iter().map(Index::from_index_url))
.chain(args.extra_index_url.into_iter().flatten().map(Index::from))
.chain(args.index_url.into_iter().map(Index::from))
.chain(index.into_iter().flatten())
.chain(
extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(index_url.into_iter().map(Index::from_index_url))
.chain(extra_index_url.into_iter().flatten().map(Index::from))
.chain(index_url.into_iter().map(Index::from))
.collect(),
args.find_links
.combine(find_links)
.into_iter()
.flatten()
.map(Index::from_find_links)
.map(Index::from)
.collect(),
args.no_index.combine(no_index).unwrap_or_default(),
),

View File

@ -12298,7 +12298,7 @@ fn lock_named_index_cli() -> Result<()> {
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0", "jinja2"]
dependencies = ["jinja2==3.1.2"]
[tool.uv.sources]
jinja2 = { index = "pytorch" }
@ -12306,7 +12306,7 @@ fn lock_named_index_cli() -> Result<()> {
)?;
// The package references a non-existent index.
uv_snapshot!(context.filters(), context.lock(), @r###"
uv_snapshot!(context.filters(), context.lock().env_remove("UV_EXCLUDE_NEWER"), @r###"
success: false
exit_code: 2
----- stdout -----
@ -12317,16 +12317,14 @@ fn lock_named_index_cli() -> Result<()> {
Caused by: Package `jinja2` references an undeclared index: `pytorch`
"###);
// This also isn't supported right now; you need to specify the index in the `pyproject.toml`.
uv_snapshot!(context.filters(), context.lock().arg("--index").arg("pytorch=https://download.pytorch.org/whl/cu121"), @r###"
success: false
exit_code: 2
// But it's fine if it comes from the CLI.
uv_snapshot!(context.filters(), context.lock().arg("--index").arg("pytorch=https://download.pytorch.org/whl/cu121").env_remove("UV_EXCLUDE_NEWER"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
error: Failed to build: `project @ file://[TEMP_DIR]/`
Caused by: Failed to parse entry for: `jinja2`
Caused by: Package `jinja2` references an undeclared index: `pytorch`
Resolved 3 packages in [TIME]
"###);
Ok(())

View File

@ -115,6 +115,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -261,6 +262,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -408,6 +410,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -587,6 +590,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -864,6 +868,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -1035,6 +1040,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
Index {
name: None,
@ -1062,6 +1068,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -1210,6 +1217,9 @@ fn resolve_index_url() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -1237,6 +1247,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
Index {
name: None,
@ -1264,6 +1275,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -1436,6 +1448,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
],
no_index: true,
@ -1740,6 +1753,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
Index {
name: None,
@ -1767,6 +1781,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
],
flat_index: [],
@ -1913,6 +1928,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
Index {
name: None,
@ -1940,6 +1956,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
],
flat_index: [],
@ -2943,6 +2960,7 @@ fn resolve_both() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -3116,6 +3134,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -3778,6 +3797,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -3805,6 +3827,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
],
flat_index: [],
@ -3951,6 +3974,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -3978,6 +4004,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: None,
},
],
flat_index: [],
@ -4130,6 +4157,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -4157,6 +4187,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -4304,6 +4335,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -4331,6 +4365,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -4485,6 +4520,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -4512,6 +4550,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],
@ -4659,6 +4698,9 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: false,
origin: Some(
Cli,
),
},
Index {
name: None,
@ -4686,6 +4728,7 @@ fn index_priority() -> anyhow::Result<()> {
),
explicit: false,
default: true,
origin: None,
},
],
flat_index: [],