mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 04:17:37 +00:00
Remove the flat index types (#7759)
## Summary I think these really don't pull their weight.
This commit is contained in:
parent
d31b995511
commit
a034a8b83b
21 changed files with 158 additions and 315 deletions
|
|
@ -14,7 +14,7 @@ use uv_configuration::{
|
||||||
ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
|
ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
|
||||||
TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
|
TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
|
||||||
};
|
};
|
||||||
use uv_distribution_types::{FlatIndexLocation, Index, IndexUrl};
|
use uv_distribution_types::{Index, IndexUrl};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
use uv_pep508::Requirement;
|
use uv_pep508::Requirement;
|
||||||
use uv_pypi_types::VerbatimParsedUrl;
|
use uv_pypi_types::VerbatimParsedUrl;
|
||||||
|
|
@ -786,18 +786,6 @@ fn parse_index_url(input: &str) -> Result<Maybe<IndexUrl>, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a string into an [`FlatIndexLocation`], mapping the empty string to `None`.
|
|
||||||
fn parse_flat_index(input: &str) -> Result<Maybe<FlatIndexLocation>, String> {
|
|
||||||
if input.is_empty() {
|
|
||||||
Ok(Maybe::None)
|
|
||||||
} else {
|
|
||||||
match FlatIndexLocation::from_str(input) {
|
|
||||||
Ok(url) => Ok(Maybe::Some(url)),
|
|
||||||
Err(err) => Err(err.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a string into an [`Index`], mapping the empty string to `None`.
|
/// Parse a string into an [`Index`], mapping the empty string to `None`.
|
||||||
fn parse_index_source(input: &str) -> Result<Maybe<Index>, String> {
|
fn parse_index_source(input: &str) -> Result<Maybe<Index>, String> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
|
|
@ -3897,10 +3885,10 @@ pub struct IndexArgs {
|
||||||
long,
|
long,
|
||||||
short,
|
short,
|
||||||
env = EnvVars::UV_FIND_LINKS,
|
env = EnvVars::UV_FIND_LINKS,
|
||||||
value_parser = parse_flat_index,
|
value_parser = parse_index_url,
|
||||||
help_heading = "Index options"
|
help_heading = "Index options"
|
||||||
)]
|
)]
|
||||||
pub find_links: Option<Vec<Maybe<FlatIndexLocation>>>,
|
pub find_links: Option<Vec<Maybe<IndexUrl>>>,
|
||||||
|
|
||||||
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
|
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
|
||||||
/// provided via `--find-links`.
|
/// provided via `--find-links`.
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ use reqwest::Response;
|
||||||
use tracing::{debug, info_span, warn, Instrument};
|
use tracing::{debug, info_span, warn, Instrument};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::cached_client::{CacheControl, CachedClientError};
|
|
||||||
use crate::html::SimpleHtml;
|
|
||||||
use crate::{Connectivity, Error, ErrorKind, OwnedArchive, RegistryClient};
|
|
||||||
use uv_cache::{Cache, CacheBucket};
|
use uv_cache::{Cache, CacheBucket};
|
||||||
use uv_cache_key::cache_digest;
|
use uv_cache_key::cache_digest;
|
||||||
use uv_distribution_filename::DistFilename;
|
use uv_distribution_filename::DistFilename;
|
||||||
use uv_distribution_types::{File, FileLocation, FlatIndexLocation, IndexUrl, UrlString};
|
use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString};
|
||||||
|
|
||||||
|
use crate::cached_client::{CacheControl, CachedClientError};
|
||||||
|
use crate::html::SimpleHtml;
|
||||||
|
use crate::{Connectivity, Error, ErrorKind, OwnedArchive, RegistryClient};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FlatIndexError {
|
pub enum FlatIndexError {
|
||||||
|
|
@ -94,19 +95,19 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
&self,
|
&self,
|
||||||
indexes: impl Iterator<Item = &FlatIndexLocation>,
|
indexes: impl Iterator<Item = &IndexUrl>,
|
||||||
) -> Result<FlatIndexEntries, FlatIndexError> {
|
) -> Result<FlatIndexEntries, FlatIndexError> {
|
||||||
let mut fetches = futures::stream::iter(indexes)
|
let mut fetches = futures::stream::iter(indexes)
|
||||||
.map(|index| async move {
|
.map(|index| async move {
|
||||||
let entries = match index {
|
let entries = match index {
|
||||||
FlatIndexLocation::Path(url) => {
|
IndexUrl::Path(url) => {
|
||||||
let path = url
|
let path = url
|
||||||
.to_file_path()
|
.to_file_path()
|
||||||
.map_err(|()| FlatIndexError::NonFileUrl(url.to_url()))?;
|
.map_err(|()| FlatIndexError::NonFileUrl(url.to_url()))?;
|
||||||
Self::read_from_directory(&path, index)
|
Self::read_from_directory(&path, index)
|
||||||
.map_err(|err| FlatIndexError::FindLinksDirectory(path.clone(), err))?
|
.map_err(|err| FlatIndexError::FindLinksDirectory(path.clone(), err))?
|
||||||
}
|
}
|
||||||
FlatIndexLocation::Url(url) => self
|
IndexUrl::Pypi(url) | IndexUrl::Url(url) => self
|
||||||
.read_from_url(url, index)
|
.read_from_url(url, index)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| FlatIndexError::FindLinksUrl(url.to_url(), err))?,
|
.map_err(|err| FlatIndexError::FindLinksUrl(url.to_url(), err))?,
|
||||||
|
|
@ -136,7 +137,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
async fn read_from_url(
|
async fn read_from_url(
|
||||||
&self,
|
&self,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
flat_index: &FlatIndexLocation,
|
flat_index: &IndexUrl,
|
||||||
) -> Result<FlatIndexEntries, Error> {
|
) -> Result<FlatIndexEntries, Error> {
|
||||||
let cache_entry = self.cache.entry(
|
let cache_entry = self.cache.entry(
|
||||||
CacheBucket::FlatIndex,
|
CacheBucket::FlatIndex,
|
||||||
|
|
@ -210,7 +211,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
Some((
|
Some((
|
||||||
DistFilename::try_from_normalized_filename(&file.filename)?,
|
DistFilename::try_from_normalized_filename(&file.filename)?,
|
||||||
file,
|
file,
|
||||||
IndexUrl::from(flat_index.clone()),
|
flat_index.clone(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -226,7 +227,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
/// Read a flat remote index from a `--find-links` directory.
|
/// Read a flat remote index from a `--find-links` directory.
|
||||||
fn read_from_directory(
|
fn read_from_directory(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
flat_index: &FlatIndexLocation,
|
flat_index: &IndexUrl,
|
||||||
) -> Result<FlatIndexEntries, FindLinksDirectoryError> {
|
) -> Result<FlatIndexEntries, FindLinksDirectoryError> {
|
||||||
let mut dists = Vec::new();
|
let mut dists = Vec::new();
|
||||||
for entry in fs_err::read_dir(path)? {
|
for entry in fs_err::read_dir(path)? {
|
||||||
|
|
@ -279,7 +280,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
dists.push((filename, file, IndexUrl::from(flat_index.clone())));
|
dists.push((filename, file, flat_index.clone()));
|
||||||
}
|
}
|
||||||
Ok(FlatIndexEntries::from_entries(dists))
|
Ok(FlatIndexEntries::from_entries(dists))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,16 @@ impl Index {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize an [`Index`] from a pip-style `--find-links`.
|
||||||
|
pub fn from_find_links(url: IndexUrl) -> Self {
|
||||||
|
Self {
|
||||||
|
url,
|
||||||
|
name: None,
|
||||||
|
explicit: false,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the [`IndexUrl`] of the index.
|
/// Return the [`IndexUrl`] of the index.
|
||||||
pub fn url(&self) -> &IndexUrl {
|
pub fn url(&self) -> &IndexUrl {
|
||||||
&self.url
|
&self.url
|
||||||
|
|
|
||||||
|
|
@ -99,15 +99,6 @@ impl Verbatim for IndexUrl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FlatIndexLocation> for IndexUrl {
|
|
||||||
fn from(location: FlatIndexLocation) -> Self {
|
|
||||||
match location {
|
|
||||||
FlatIndexLocation::Path(url) => Self::Path(url),
|
|
||||||
FlatIndexLocation::Url(url) => Self::Url(url),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error that can occur when parsing an [`IndexUrl`].
|
/// An error that can occur when parsing an [`IndexUrl`].
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum IndexUrlError {
|
pub enum IndexUrlError {
|
||||||
|
|
@ -185,117 +176,6 @@ impl Deref for IndexUrl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A directory with distributions or a URL to an HTML file with a flat listing of distributions.
|
|
||||||
///
|
|
||||||
/// Also known as `--find-links`.
|
|
||||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
|
||||||
pub enum FlatIndexLocation {
|
|
||||||
Path(VerbatimUrl),
|
|
||||||
Url(VerbatimUrl),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
|
||||||
impl schemars::JsonSchema for FlatIndexLocation {
|
|
||||||
fn schema_name() -> String {
|
|
||||||
"FlatIndexLocation".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
|
||||||
schemars::schema::SchemaObject {
|
|
||||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
|
||||||
metadata: Some(Box::new(schemars::schema::Metadata {
|
|
||||||
description: Some("The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.".to_string()),
|
|
||||||
..schemars::schema::Metadata::default()
|
|
||||||
})),
|
|
||||||
..schemars::schema::SchemaObject::default()
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlatIndexLocation {
|
|
||||||
/// Return the raw URL for the `--find-links` index.
|
|
||||||
pub fn url(&self) -> &Url {
|
|
||||||
match self {
|
|
||||||
Self::Url(url) => url.raw(),
|
|
||||||
Self::Path(url) => url.raw(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the redacted URL for the `--find-links` index, omitting any sensitive credentials.
|
|
||||||
pub fn redacted(&self) -> Cow<'_, Url> {
|
|
||||||
let url = self.url();
|
|
||||||
if url.username().is_empty() && url.password().is_none() {
|
|
||||||
Cow::Borrowed(url)
|
|
||||||
} else {
|
|
||||||
let mut url = url.clone();
|
|
||||||
let _ = url.set_username("");
|
|
||||||
let _ = url.set_password(None);
|
|
||||||
Cow::Owned(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FlatIndexLocation {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Url(url) => Display::fmt(url, f),
|
|
||||||
Self::Path(url) => Display::fmt(url, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Verbatim for FlatIndexLocation {
|
|
||||||
fn verbatim(&self) -> Cow<'_, str> {
|
|
||||||
match self {
|
|
||||||
Self::Url(url) => url.verbatim(),
|
|
||||||
Self::Path(url) => url.verbatim(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FlatIndexLocation {
|
|
||||||
type Err = IndexUrlError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let url = if Path::new(s).exists() {
|
|
||||||
VerbatimUrl::from_absolute_path(std::path::absolute(s)?)?
|
|
||||||
} else {
|
|
||||||
VerbatimUrl::parse_url(s)?
|
|
||||||
};
|
|
||||||
Ok(Self::from(url.with_given(s)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::ser::Serialize for FlatIndexLocation {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::ser::Serializer,
|
|
||||||
{
|
|
||||||
self.to_string().serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> serde::de::Deserialize<'de> for FlatIndexLocation {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<FlatIndexLocation, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let s = String::deserialize(deserializer)?;
|
|
||||||
FlatIndexLocation::from_str(&s).map_err(serde::de::Error::custom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<VerbatimUrl> for FlatIndexLocation {
|
|
||||||
fn from(url: VerbatimUrl) -> Self {
|
|
||||||
if url.scheme() == "file" {
|
|
||||||
Self::Path(url)
|
|
||||||
} else {
|
|
||||||
Self::Url(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The index locations to use for fetching packages. By default, uses the PyPI index.
|
/// The index locations to use for fetching packages. By default, uses the PyPI index.
|
||||||
///
|
///
|
||||||
/// This type merges the legacy `--index-url`, `--extra-index-url`, and `--find-links` options,
|
/// This type merges the legacy `--index-url`, `--extra-index-url`, and `--find-links` options,
|
||||||
|
|
@ -304,13 +184,13 @@ impl From<VerbatimUrl> for FlatIndexLocation {
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
pub struct IndexLocations {
|
pub struct IndexLocations {
|
||||||
indexes: Vec<Index>,
|
indexes: Vec<Index>,
|
||||||
flat_index: Vec<FlatIndexLocation>,
|
flat_index: Vec<Index>,
|
||||||
no_index: bool,
|
no_index: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexLocations {
|
impl IndexLocations {
|
||||||
/// Determine the index URLs to use for fetching packages.
|
/// Determine the index URLs to use for fetching packages.
|
||||||
pub fn new(indexes: Vec<Index>, flat_index: Vec<FlatIndexLocation>, no_index: bool) -> Self {
|
pub fn new(indexes: Vec<Index>, flat_index: Vec<Index>, no_index: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
indexes,
|
indexes,
|
||||||
flat_index,
|
flat_index,
|
||||||
|
|
@ -325,12 +205,7 @@ impl IndexLocations {
|
||||||
///
|
///
|
||||||
/// If the current index location has an `index` set, it will be preserved.
|
/// If the current index location has an `index` set, it will be preserved.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn combine(
|
pub fn combine(self, indexes: Vec<Index>, flat_index: Vec<Index>, no_index: bool) -> Self {
|
||||||
self,
|
|
||||||
indexes: Vec<Index>,
|
|
||||||
flat_index: Vec<FlatIndexLocation>,
|
|
||||||
no_index: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
indexes: self.indexes.into_iter().chain(indexes).collect(),
|
indexes: self.indexes.into_iter().chain(indexes).collect(),
|
||||||
flat_index: self.flat_index.into_iter().chain(flat_index).collect(),
|
flat_index: self.flat_index.into_iter().chain(flat_index).collect(),
|
||||||
|
|
@ -407,7 +282,7 @@ impl<'a> IndexLocations {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over the [`FlatIndexLocation`] entries.
|
/// Return an iterator over the [`FlatIndexLocation`] entries.
|
||||||
pub fn flat_indexes(&'a self) -> impl Iterator<Item = &'a FlatIndexLocation> + 'a {
|
pub fn flat_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
|
||||||
self.flat_index.iter()
|
self.flat_index.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,13 +301,12 @@ impl<'a> IndexLocations {
|
||||||
|
|
||||||
/// Return an iterator over all allowed [`Index`] entries.
|
/// Return an iterator over all allowed [`Index`] entries.
|
||||||
///
|
///
|
||||||
/// This includes both explicit and implicit indexes, as well as the default index (but _not_
|
/// This includes explicit indexes, implicit indexes flat indexes, and the default index.
|
||||||
/// the flat indexes).
|
|
||||||
///
|
///
|
||||||
/// If `no_index` was enabled, then this always returns an empty
|
/// If `no_index` was enabled, then this always returns an empty iterator.
|
||||||
/// iterator.
|
|
||||||
pub fn allowed_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
|
pub fn allowed_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
|
||||||
self.explicit_indexes()
|
self.flat_indexes()
|
||||||
|
.chain(self.explicit_indexes())
|
||||||
.chain(self.implicit_indexes())
|
.chain(self.implicit_indexes())
|
||||||
.chain(self.default_index())
|
.chain(self.default_index())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ use crate::source::{HttpRevisionPointer, LocalRevisionPointer, HTTP_REVISION, LO
|
||||||
|
|
||||||
/// An entry in the [`RegistryWheelIndex`].
|
/// An entry in the [`RegistryWheelIndex`].
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub struct IndexEntry {
|
pub struct IndexEntry<'index> {
|
||||||
/// The cached distribution.
|
/// The cached distribution.
|
||||||
pub dist: CachedRegistryDist,
|
pub dist: CachedRegistryDist,
|
||||||
/// The index from which the wheel was downloaded.
|
|
||||||
pub index: Index,
|
|
||||||
/// Whether the wheel was built from source (true), or downloaded from the registry directly (false).
|
/// Whether the wheel was built from source (true), or downloaded from the registry directly (false).
|
||||||
pub built: bool,
|
pub built: bool,
|
||||||
|
/// The index from which the wheel was downloaded.
|
||||||
|
pub index: &'index Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A local index of distributions that originate from a registry, like `PyPI`.
|
/// A local index of distributions that originate from a registry, like `PyPI`.
|
||||||
|
|
@ -30,7 +30,7 @@ pub struct RegistryWheelIndex<'a> {
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
index_locations: &'a IndexLocations,
|
index_locations: &'a IndexLocations,
|
||||||
hasher: &'a HashStrategy,
|
hasher: &'a HashStrategy,
|
||||||
index: FxHashMap<&'a PackageName, Vec<IndexEntry>>,
|
index: FxHashMap<&'a PackageName, Vec<IndexEntry<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RegistryWheelIndex<'a> {
|
impl<'a> RegistryWheelIndex<'a> {
|
||||||
|
|
@ -73,21 +73,16 @@ impl<'a> RegistryWheelIndex<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a package to the index by reading from the cache.
|
/// Add a package to the index by reading from the cache.
|
||||||
fn index(
|
fn index<'index>(
|
||||||
package: &PackageName,
|
package: &PackageName,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
index_locations: &IndexLocations,
|
index_locations: &'index IndexLocations,
|
||||||
hasher: &HashStrategy,
|
hasher: &HashStrategy,
|
||||||
) -> Vec<IndexEntry> {
|
) -> Vec<IndexEntry<'index>> {
|
||||||
let mut entries = vec![];
|
let mut entries = vec![];
|
||||||
|
|
||||||
let flat_index_urls: Vec<Index> = index_locations
|
for index in index_locations.allowed_indexes() {
|
||||||
.flat_indexes()
|
|
||||||
.map(|flat_index| Index::from_extra_index_url(IndexUrl::from(flat_index.clone())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for index in index_locations.indexes().chain(flat_index_urls.iter()) {
|
|
||||||
// Index all the wheels that were downloaded directly from the registry.
|
// Index all the wheels that were downloaded directly from the registry.
|
||||||
let wheel_dir = cache.shard(
|
let wheel_dir = cache.shard(
|
||||||
CacheBucket::Wheels,
|
CacheBucket::Wheels,
|
||||||
|
|
@ -117,7 +112,7 @@ impl<'a> RegistryWheelIndex<'a> {
|
||||||
) {
|
) {
|
||||||
entries.push(IndexEntry {
|
entries.push(IndexEntry {
|
||||||
dist: wheel.into_registry_dist(),
|
dist: wheel.into_registry_dist(),
|
||||||
index: index.clone(),
|
index,
|
||||||
built: false,
|
built: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +139,7 @@ impl<'a> RegistryWheelIndex<'a> {
|
||||||
) {
|
) {
|
||||||
entries.push(IndexEntry {
|
entries.push(IndexEntry {
|
||||||
dist: wheel.into_registry_dist(),
|
dist: wheel.into_registry_dist(),
|
||||||
index: index.clone(),
|
index,
|
||||||
built: false,
|
built: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +195,7 @@ impl<'a> RegistryWheelIndex<'a> {
|
||||||
) {
|
) {
|
||||||
entries.push(IndexEntry {
|
entries.push(IndexEntry {
|
||||||
dist: wheel.into_registry_dist(),
|
dist: wheel.into_registry_dist(),
|
||||||
index: index.clone(),
|
index,
|
||||||
built: true,
|
built: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,11 @@ use std::path::{Path, PathBuf};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use uv_cache_key::CanonicalUrl;
|
use uv_cache_key::CanonicalUrl;
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
use uv_configuration::{NoBinary, NoBuild};
|
use uv_configuration::{NoBinary, NoBuild};
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
FlatIndexLocation, IndexUrl, NameRequirementSpecification, UnresolvedRequirement,
|
IndexUrl, NameRequirementSpecification, UnresolvedRequirement,
|
||||||
UnresolvedRequirementSpecification,
|
UnresolvedRequirementSpecification,
|
||||||
};
|
};
|
||||||
use uv_fs::{Simplified, CWD};
|
use uv_fs::{Simplified, CWD};
|
||||||
|
|
@ -71,7 +70,7 @@ pub struct RequirementsSpecification {
|
||||||
/// Whether to disallow index usage.
|
/// Whether to disallow index usage.
|
||||||
pub no_index: bool,
|
pub no_index: bool,
|
||||||
/// The `--find-links` locations to use for fetching packages.
|
/// The `--find-links` locations to use for fetching packages.
|
||||||
pub find_links: Vec<FlatIndexLocation>,
|
pub find_links: Vec<IndexUrl>,
|
||||||
/// The `--no-binary` flags to enforce when selecting distributions.
|
/// The `--no-binary` flags to enforce when selecting distributions.
|
||||||
pub no_binary: NoBinary,
|
pub no_binary: NoBinary,
|
||||||
/// The `--no-build` flags to enforce when selecting distributions.
|
/// The `--no-build` flags to enforce when selecting distributions.
|
||||||
|
|
@ -142,7 +141,7 @@ impl RequirementsSpecification {
|
||||||
find_links: requirements_txt
|
find_links: requirements_txt
|
||||||
.find_links
|
.find_links
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(FlatIndexLocation::from)
|
.map(IndexUrl::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
no_binary: requirements_txt.no_binary,
|
no_binary: requirements_txt.no_binary,
|
||||||
no_build: requirements_txt.only_binary,
|
no_build: requirements_txt.only_binary,
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,9 @@ use uv_distribution::DistributionDatabase;
|
||||||
use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename};
|
use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename};
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
||||||
Dist, DistributionMetadata, FileLocation, FlatIndexLocation, GitSourceDist, IndexLocations,
|
Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, IndexUrl, Name,
|
||||||
IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist,
|
||||||
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, StaticMetadata, ToUrlError,
|
RemoteSource, Resolution, ResolvedDist, StaticMetadata, ToUrlError, UrlString,
|
||||||
UrlString,
|
|
||||||
};
|
};
|
||||||
use uv_fs::{relative_to, PortablePath, PortablePathBuf};
|
use uv_fs::{relative_to, PortablePath, PortablePathBuf};
|
||||||
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
|
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
|
||||||
|
|
@ -1065,16 +1064,6 @@ impl Lock {
|
||||||
}
|
}
|
||||||
IndexUrl::Path(_) => None,
|
IndexUrl::Path(_) => None,
|
||||||
})
|
})
|
||||||
.chain(
|
|
||||||
locations
|
|
||||||
.flat_indexes()
|
|
||||||
.filter_map(|index_url| match index_url {
|
|
||||||
FlatIndexLocation::Url(_) => {
|
|
||||||
Some(UrlString::from(index_url.redacted()))
|
|
||||||
}
|
|
||||||
FlatIndexLocation::Path(_) => None,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<BTreeSet<_>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1091,20 +1080,6 @@ impl Lock {
|
||||||
Some(path)
|
Some(path)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.chain(
|
|
||||||
locations
|
|
||||||
.flat_indexes()
|
|
||||||
.filter_map(|index_url| match index_url {
|
|
||||||
FlatIndexLocation::Url(_) => None,
|
|
||||||
FlatIndexLocation::Path(index_url) => {
|
|
||||||
let path = index_url.to_file_path().ok()?;
|
|
||||||
let path = relative_to(&path, workspace.install_path())
|
|
||||||
.or_else(|_| std::path::absolute(path))
|
|
||||||
.ok()?;
|
|
||||||
Some(path)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<BTreeSet<_>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use uv_configuration::{
|
||||||
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
|
||||||
TrustedHost, TrustedPublishing,
|
TrustedHost, TrustedPublishing,
|
||||||
};
|
};
|
||||||
use uv_distribution_types::{FlatIndexLocation, Index, IndexUrl, StaticMetadata};
|
use uv_distribution_types::{Index, IndexUrl, StaticMetadata};
|
||||||
use uv_install_wheel::linker::LinkMode;
|
use uv_install_wheel::linker::LinkMode;
|
||||||
use uv_macros::{CombineOptions, OptionsMetadata};
|
use uv_macros::{CombineOptions, OptionsMetadata};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
@ -234,7 +234,7 @@ pub struct InstallerOptions {
|
||||||
pub index_url: Option<IndexUrl>,
|
pub index_url: Option<IndexUrl>,
|
||||||
pub extra_index_url: Option<Vec<IndexUrl>>,
|
pub extra_index_url: Option<Vec<IndexUrl>>,
|
||||||
pub no_index: Option<bool>,
|
pub no_index: Option<bool>,
|
||||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
pub find_links: Option<Vec<IndexUrl>>,
|
||||||
pub index_strategy: Option<IndexStrategy>,
|
pub index_strategy: Option<IndexStrategy>,
|
||||||
pub keyring_provider: Option<KeyringProviderType>,
|
pub keyring_provider: Option<KeyringProviderType>,
|
||||||
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||||
|
|
@ -259,7 +259,7 @@ pub struct ResolverOptions {
|
||||||
pub index_url: Option<IndexUrl>,
|
pub index_url: Option<IndexUrl>,
|
||||||
pub extra_index_url: Option<Vec<IndexUrl>>,
|
pub extra_index_url: Option<Vec<IndexUrl>>,
|
||||||
pub no_index: Option<bool>,
|
pub no_index: Option<bool>,
|
||||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
pub find_links: Option<Vec<IndexUrl>>,
|
||||||
pub index_strategy: Option<IndexStrategy>,
|
pub index_strategy: Option<IndexStrategy>,
|
||||||
pub keyring_provider: Option<KeyringProviderType>,
|
pub keyring_provider: Option<KeyringProviderType>,
|
||||||
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||||
|
|
@ -386,7 +386,7 @@ pub struct ResolverInstallerOptions {
|
||||||
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
|
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
pub find_links: Option<Vec<IndexUrl>>,
|
||||||
/// The strategy to use when resolving against multiple index URLs.
|
/// 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
|
/// By default, uv will stop at the first index on which a given package is available, and
|
||||||
|
|
@ -798,7 +798,7 @@ pub struct PipOptions {
|
||||||
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
|
find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
pub find_links: Option<Vec<IndexUrl>>,
|
||||||
/// The strategy to use when resolving against multiple index URLs.
|
/// 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
|
/// By default, uv will stop at the first index on which a given package is available, and
|
||||||
|
|
@ -1414,7 +1414,7 @@ pub struct ToolOptions {
|
||||||
pub index_url: Option<IndexUrl>,
|
pub index_url: Option<IndexUrl>,
|
||||||
pub extra_index_url: Option<Vec<IndexUrl>>,
|
pub extra_index_url: Option<Vec<IndexUrl>>,
|
||||||
pub no_index: Option<bool>,
|
pub no_index: Option<bool>,
|
||||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
pub find_links: Option<Vec<IndexUrl>>,
|
||||||
pub index_strategy: Option<IndexStrategy>,
|
pub index_strategy: Option<IndexStrategy>,
|
||||||
pub keyring_provider: Option<KeyringProviderType>,
|
pub keyring_provider: Option<KeyringProviderType>,
|
||||||
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
pub allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||||
|
|
@ -1520,7 +1520,7 @@ pub struct OptionsWire {
|
||||||
index_url: Option<IndexUrl>,
|
index_url: Option<IndexUrl>,
|
||||||
extra_index_url: Option<Vec<IndexUrl>>,
|
extra_index_url: Option<Vec<IndexUrl>>,
|
||||||
no_index: Option<bool>,
|
no_index: Option<bool>,
|
||||||
find_links: Option<Vec<FlatIndexLocation>>,
|
find_links: Option<Vec<IndexUrl>>,
|
||||||
index_strategy: Option<IndexStrategy>,
|
index_strategy: Option<IndexStrategy>,
|
||||||
keyring_provider: Option<KeyringProviderType>,
|
keyring_provider: Option<KeyringProviderType>,
|
||||||
allow_insecure_host: Option<Vec<TrustedHost>>,
|
allow_insecure_host: Option<Vec<TrustedHost>>,
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ use std::io::Write as _;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use uv_distribution_filename::SourceDistExtension;
|
use uv_distribution_filename::SourceDistExtension;
|
||||||
use uv_distribution_types::{DependencyMetadata, IndexLocations};
|
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations};
|
||||||
use uv_install_wheel::linker::LinkMode;
|
use uv_install_wheel::linker::LinkMode;
|
||||||
|
|
||||||
use uv_auth::{store_credentials, store_credentials_from_url};
|
use uv_auth::store_credentials;
|
||||||
use uv_cache::{Cache, CacheBucket};
|
use uv_cache::{Cache, CacheBucket};
|
||||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
|
|
@ -405,9 +406,6 @@ async fn build_package(
|
||||||
store_credentials(index.raw_url(), credentials);
|
store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read build constraints.
|
// Read build constraints.
|
||||||
let build_constraints = operations::read_constraints(build_constraints, client_builder).await?;
|
let build_constraints = operations::read_constraints(build_constraints, client_builder).await?;
|
||||||
|
|
@ -459,7 +457,9 @@ async fn build_package(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, None, &hasher, build_options)
|
FlatIndex::from_entries(entries, None, &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ pub(crate) async fn pip_compile(
|
||||||
.map(Index::from_extra_index_url)
|
.map(Index::from_extra_index_url)
|
||||||
.chain(index_url.map(Index::from_index_url))
|
.chain(index_url.map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
find_links,
|
find_links.into_iter().map(Index::from_find_links).collect(),
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -288,9 +288,6 @@ pub(crate) async fn pip_compile(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
@ -312,7 +309,9 @@ pub(crate) async fn pip_compile(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, &cache);
|
let client = FlatIndexClient::new(&client, &cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, tags.as_deref(), &hasher, &build_options)
|
FlatIndex::from_entries(entries, tags.as_deref(), &hasher, &build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -470,7 +469,7 @@ pub(crate) async fn pip_compile(
|
||||||
// If necessary, include the `--find-links` locations.
|
// If necessary, include the `--find-links` locations.
|
||||||
if include_find_links {
|
if include_find_links {
|
||||||
for flat_index in index_locations.flat_indexes() {
|
for flat_index in index_locations.flat_indexes() {
|
||||||
writeln!(writer, "--find-links {}", flat_index.verbatim())?;
|
writeln!(writer, "--find-links {}", flat_index.url().verbatim())?;
|
||||||
wrote_preamble = true;
|
wrote_preamble = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ pub(crate) async fn pip_install(
|
||||||
.map(Index::from_extra_index_url)
|
.map(Index::from_extra_index_url)
|
||||||
.chain(index_url.map(Index::from_index_url))
|
.chain(index_url.map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
find_links,
|
find_links.into_iter().map(Index::from_find_links).collect(),
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -290,9 +290,6 @@ pub(crate) async fn pip_install(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
@ -309,7 +306,9 @@ pub(crate) async fn pip_install(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, &cache);
|
let client = FlatIndexClient::new(&client, &cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options)
|
FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ pub(crate) async fn pip_sync(
|
||||||
.map(Index::from_extra_index_url)
|
.map(Index::from_extra_index_url)
|
||||||
.chain(index_url.map(Index::from_index_url))
|
.chain(index_url.map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
find_links,
|
find_links.into_iter().map(Index::from_find_links).collect(),
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -226,9 +226,6 @@ pub(crate) async fn pip_sync(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
@ -245,7 +242,9 @@ pub(crate) async fn pip_sync(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, &cache);
|
let client = FlatIndexClient::new(&client, &cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options)
|
FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -251,9 +251,6 @@ pub(crate) async fn add(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in settings.index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
@ -282,7 +279,7 @@ pub(crate) async fn add(
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client
|
let entries = client
|
||||||
.fetch(settings.index_locations.flat_indexes())
|
.fetch(settings.index_locations.flat_indexes().map(Index::url))
|
||||||
.await?;
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(&tags), &hasher, &settings.build_options)
|
FlatIndex::from_entries(entries, Some(&tags), &hasher, &settings.build_options)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use uv_configuration::{
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
DependencyMetadata, IndexLocations, NameRequirementSpecification,
|
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification,
|
||||||
UnresolvedRequirementSpecification,
|
UnresolvedRequirementSpecification,
|
||||||
};
|
};
|
||||||
use uv_git::ResolvedRepositoryReference;
|
use uv_git::ResolvedRepositoryReference;
|
||||||
|
|
@ -369,9 +369,6 @@ async fn do_lock(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
|
@ -414,7 +411,9 @@ async fn do_lock(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, None, &hasher, build_options)
|
FlatIndex::from_entries(entries, None, &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -651,9 +651,6 @@ pub(crate) async fn resolve_names(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
|
@ -803,9 +800,6 @@ pub(crate) async fn resolve_environment<'a>(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
|
@ -866,7 +860,9 @@ pub(crate) async fn resolve_environment<'a>(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -966,9 +962,6 @@ pub(crate) async fn sync_environment(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
|
@ -1001,7 +994,9 @@ pub(crate) async fn sync_environment(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1159,9 +1154,6 @@ pub(crate) async fn update_environment(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
|
@ -1208,7 +1200,9 @@ pub(crate) async fn update_environment(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1387,7 +1381,11 @@ fn warn_on_requirements_txt_setting(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for find_link in find_links {
|
for find_link in find_links {
|
||||||
if !settings.index_locations.flat_indexes().contains(find_link) {
|
if !settings
|
||||||
|
.index_locations
|
||||||
|
.flat_indexes()
|
||||||
|
.any(|index| index.url() == find_link)
|
||||||
|
{
|
||||||
warn_user_once!(
|
warn_user_once!(
|
||||||
"Ignoring `--find-links` from requirements file: `{find_link}`. Instead, use the `--find-links` command-line argument, or set `find-links` in a `uv.toml` or `pyproject.toml` file.`"
|
"Ignoring `--find-links` from requirements file: `{find_link}`. Instead, use the `--find-links` command-line argument, or set `find-links` in a `uv.toml` or `pyproject.toml` file.`"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,10 @@
|
||||||
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
|
||||||
use crate::commands::pip::operations;
|
|
||||||
use crate::commands::pip::operations::Modifications;
|
|
||||||
use crate::commands::project::lock::do_safe_lock;
|
|
||||||
use crate::commands::project::{ProjectError, SharedState};
|
|
||||||
use crate::commands::{diagnostics, pip, project, ExitStatus};
|
|
||||||
use crate::printer::Printer;
|
|
||||||
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings};
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use uv_auth::{store_credentials, store_credentials_from_url};
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use uv_auth::store_credentials;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
|
|
@ -19,7 +12,7 @@ use uv_configuration::{
|
||||||
HashCheckingMode, InstallOptions,
|
HashCheckingMode, InstallOptions,
|
||||||
};
|
};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution_types::{DirectorySourceDist, Dist, ResolvedDist, SourceDist};
|
use uv_distribution_types::{DirectorySourceDist, Dist, Index, ResolvedDist, SourceDist};
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
||||||
use uv_pep508::{MarkerTree, Requirement, VersionOrUrl};
|
use uv_pep508::{MarkerTree, Requirement, VersionOrUrl};
|
||||||
|
|
@ -33,6 +26,15 @@ use uv_warnings::warn_user;
|
||||||
use uv_workspace::pyproject::{Source, Sources, ToolUvSources};
|
use uv_workspace::pyproject::{Source, Sources, ToolUvSources};
|
||||||
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
|
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
|
||||||
|
|
||||||
|
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
|
||||||
|
use crate::commands::pip::operations;
|
||||||
|
use crate::commands::pip::operations::Modifications;
|
||||||
|
use crate::commands::project::lock::do_safe_lock;
|
||||||
|
use crate::commands::project::{ProjectError, SharedState};
|
||||||
|
use crate::commands::{diagnostics, pip, project, ExitStatus};
|
||||||
|
use crate::printer::Printer;
|
||||||
|
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings};
|
||||||
|
|
||||||
/// Sync the project environment.
|
/// Sync the project environment.
|
||||||
#[allow(clippy::fn_params_excessive_bools)]
|
#[allow(clippy::fn_params_excessive_bools)]
|
||||||
pub(crate) async fn sync(
|
pub(crate) async fn sync(
|
||||||
|
|
@ -282,9 +284,6 @@ pub(super) async fn do_sync(
|
||||||
store_credentials(index.raw_url(), credentials);
|
store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate credentials from the workspace.
|
// Populate credentials from the workspace.
|
||||||
store_credentials_from_workspace(target.workspace());
|
store_credentials_from_workspace(target.workspace());
|
||||||
|
|
@ -322,7 +321,9 @@ pub(super) async fn do_sync(
|
||||||
// Resolve the flat indexes from `--find-links`.
|
// Resolve the flat indexes from `--find-links`.
|
||||||
let flat_index = {
|
let flat_index = {
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client.fetch(index_locations.flat_indexes()).await?;
|
let entries = client
|
||||||
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
|
.await?;
|
||||||
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
FlatIndex::from_entries(entries, Some(tags), &hasher, build_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use uv_configuration::{
|
||||||
NoBinary, NoBuild, SourceStrategy, TrustedHost,
|
NoBinary, NoBuild, SourceStrategy, TrustedHost,
|
||||||
};
|
};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_distribution_types::{DependencyMetadata, IndexLocations};
|
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_install_wheel::linker::LinkMode;
|
use uv_install_wheel::linker::LinkMode;
|
||||||
use uv_pypi_types::Requirement;
|
use uv_pypi_types::Requirement;
|
||||||
|
|
@ -233,9 +233,6 @@ async fn venv_impl(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
if managed {
|
if managed {
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -287,9 +284,6 @@ async fn venv_impl(
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials);
|
uv_auth::store_credentials(index.raw_url(), credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in index_locations.flat_indexes() {
|
|
||||||
uv_auth::store_credentials_from_url(index.url());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate a client.
|
// Instantiate a client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)
|
let client = RegistryClientBuilder::try_from(client_builder)
|
||||||
|
|
@ -308,7 +302,7 @@ async fn venv_impl(
|
||||||
let tags = interpreter.tags().map_err(VenvError::Tags)?;
|
let tags = interpreter.tags().map_err(VenvError::Tags)?;
|
||||||
let client = FlatIndexClient::new(&client, cache);
|
let client = FlatIndexClient::new(&client, cache);
|
||||||
let entries = client
|
let entries = client
|
||||||
.fetch(index_locations.flat_indexes())
|
.fetch(index_locations.flat_indexes().map(Index::url))
|
||||||
.await
|
.await
|
||||||
.map_err(VenvError::FlatIndex)?;
|
.map_err(VenvError::FlatIndex)?;
|
||||||
FlatIndex::from_entries(
|
FlatIndex::from_entries(
|
||||||
|
|
|
||||||
|
|
@ -1981,7 +1981,12 @@ impl From<ResolverOptions> for ResolverSettings {
|
||||||
)
|
)
|
||||||
.chain(value.index_url.into_iter().map(Index::from_index_url))
|
.chain(value.index_url.into_iter().map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
value.find_links.unwrap_or_default(),
|
value
|
||||||
|
.find_links
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Index::from_find_links)
|
||||||
|
.collect(),
|
||||||
value.no_index.unwrap_or_default(),
|
value.no_index.unwrap_or_default(),
|
||||||
),
|
),
|
||||||
resolution: value.resolution.unwrap_or_default(),
|
resolution: value.resolution.unwrap_or_default(),
|
||||||
|
|
@ -2119,7 +2124,12 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
|
||||||
)
|
)
|
||||||
.chain(value.index_url.into_iter().map(Index::from_index_url))
|
.chain(value.index_url.into_iter().map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
value.find_links.unwrap_or_default(),
|
value
|
||||||
|
.find_links
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Index::from_find_links)
|
||||||
|
.collect(),
|
||||||
value.no_index.unwrap_or_default(),
|
value.no_index.unwrap_or_default(),
|
||||||
),
|
),
|
||||||
resolution: value.resolution.unwrap_or_default(),
|
resolution: value.resolution.unwrap_or_default(),
|
||||||
|
|
@ -2353,7 +2363,12 @@ impl PipSettings {
|
||||||
)
|
)
|
||||||
.chain(index_url.into_iter().map(Index::from_index_url))
|
.chain(index_url.into_iter().map(Index::from_index_url))
|
||||||
.collect(),
|
.collect(),
|
||||||
args.find_links.combine(find_links).unwrap_or_default(),
|
args.find_links
|
||||||
|
.combine(find_links)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(Index::from_find_links)
|
||||||
|
.collect(),
|
||||||
args.no_index.combine(no_index).unwrap_or_default(),
|
args.no_index.combine(no_index).unwrap_or_default(),
|
||||||
),
|
),
|
||||||
extras: ExtrasSpecification::from_args(
|
extras: ExtrasSpecification::from_args(
|
||||||
|
|
|
||||||
|
|
@ -1410,28 +1410,33 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||||
index_locations: IndexLocations {
|
index_locations: IndexLocations {
|
||||||
indexes: [],
|
indexes: [],
|
||||||
flat_index: [
|
flat_index: [
|
||||||
Url(
|
Index {
|
||||||
VerbatimUrl {
|
name: None,
|
||||||
url: Url {
|
url: Url(
|
||||||
scheme: "https",
|
VerbatimUrl {
|
||||||
cannot_be_a_base: false,
|
url: Url {
|
||||||
username: "",
|
scheme: "https",
|
||||||
password: None,
|
cannot_be_a_base: false,
|
||||||
host: Some(
|
username: "",
|
||||||
Domain(
|
password: None,
|
||||||
"download.pytorch.org",
|
host: Some(
|
||||||
|
Domain(
|
||||||
|
"download.pytorch.org",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
port: None,
|
||||||
|
path: "/whl/torch_stable.html",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
given: Some(
|
||||||
|
"https://download.pytorch.org/whl/torch_stable.html",
|
||||||
),
|
),
|
||||||
port: None,
|
|
||||||
path: "/whl/torch_stable.html",
|
|
||||||
query: None,
|
|
||||||
fragment: None,
|
|
||||||
},
|
},
|
||||||
given: Some(
|
),
|
||||||
"https://download.pytorch.org/whl/torch_stable.html",
|
explicit: false,
|
||||||
),
|
default: false,
|
||||||
},
|
},
|
||||||
),
|
|
||||||
],
|
],
|
||||||
no_index: true,
|
no_index: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3126,7 +3126,6 @@ fn sync_explicit() -> Result<()> {
|
||||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtual environment at: .venv
|
Creating virtual environment at: .venv
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Prepared 1 package in [TIME]
|
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ idna==2.7
|
+ idna==2.7
|
||||||
"###);
|
"###);
|
||||||
|
|
|
||||||
8
uv.schema.json
generated
8
uv.schema.json
generated
|
|
@ -144,7 +144,7 @@
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/FlatIndexLocation"
|
"$ref": "#/definitions/IndexUrl"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
|
|
@ -551,10 +551,6 @@
|
||||||
"description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: - <https://peps.python.org/pep-0685/#specification/> - <https://packaging.python.org/en/latest/specifications/name-normalization/>",
|
"description": "The normalized name of an extra dependency.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: - <https://peps.python.org/pep-0685/#specification/> - <https://packaging.python.org/en/latest/specifications/name-normalization/>",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"FlatIndexLocation": {
|
|
||||||
"description": "The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"Index": {
|
"Index": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -835,7 +831,7 @@
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/FlatIndexLocation"
|
"$ref": "#/definitions/IndexUrl"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"generate-hashes": {
|
"generate-hashes": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue