mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
A variants.json support to flat indexes
This commit is contained in:
parent
224e639332
commit
ab3e837a72
11 changed files with 156 additions and 34 deletions
|
@ -7,8 +7,7 @@ use url::Url;
|
|||
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
use uv_cache_key::cache_digest;
|
||||
use uv_distribution_filename::DistFilename;
|
||||
use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString};
|
||||
use uv_distribution_types::{File, FileLocation, IndexEntryFilename, IndexUrl, UrlString};
|
||||
use uv_pypi_types::HashDigests;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
@ -40,7 +39,7 @@ pub enum FindLinksDirectoryError {
|
|||
/// An entry in a `--find-links` index.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlatIndexEntry {
|
||||
pub filename: DistFilename,
|
||||
pub filename: IndexEntryFilename,
|
||||
pub file: File,
|
||||
pub index: IndexUrl,
|
||||
}
|
||||
|
@ -238,7 +237,9 @@ impl<'a> FlatIndexClient<'a> {
|
|||
})
|
||||
.filter_map(|file| {
|
||||
Some(FlatIndexEntry {
|
||||
filename: DistFilename::try_from_normalized_filename(&file.filename)?,
|
||||
filename: IndexEntryFilename::try_from_normalized_filename(
|
||||
&file.filename,
|
||||
)?,
|
||||
file,
|
||||
index: flat_index.clone(),
|
||||
})
|
||||
|
@ -307,9 +308,10 @@ impl<'a> FlatIndexClient<'a> {
|
|||
yanked: None,
|
||||
};
|
||||
|
||||
let Some(filename) = DistFilename::try_from_normalized_filename(filename) else {
|
||||
// Try to parse as a distribution filename first
|
||||
let Some(filename) = IndexEntryFilename::try_from_normalized_filename(filename) else {
|
||||
debug!(
|
||||
"Ignoring `--find-links` entry (expected a wheel or source distribution filename): {}",
|
||||
"Ignoring `--find-links` entry (expected a wheel, source distribution, or variants.json filename): {}",
|
||||
entry.path().display()
|
||||
);
|
||||
continue;
|
||||
|
|
|
@ -1374,4 +1374,63 @@ mod tests {
|
|||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_variants_json() {
|
||||
// A variants.json without wheels doesn't make much sense, but it's sufficient to test
|
||||
// parsing.
|
||||
let text = r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Links for jinja2</h1>
|
||||
<a href="/whl/jinja2-3.1.2-variants.json#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61">jinja2-3.1.2-variants.json</a><br/>
|
||||
</body>
|
||||
</html>
|
||||
<!--TIMESTAMP 1703347410-->
|
||||
"#;
|
||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||
let result = SimpleHtml::parse(text, &base).unwrap();
|
||||
insta::assert_debug_snapshot!(result, @r#"
|
||||
SimpleHtml {
|
||||
base: BaseUrl(
|
||||
DisplaySafeUrl {
|
||||
scheme: "https",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: Some(
|
||||
Domain(
|
||||
"download.pytorch.org",
|
||||
),
|
||||
),
|
||||
port: None,
|
||||
path: "/whl/jinja2/",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
),
|
||||
files: [
|
||||
File {
|
||||
core_metadata: None,
|
||||
filename: "jinja2-3.1.2-variants.json",
|
||||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: Some(
|
||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
blake2b: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
upload_time: None,
|
||||
url: "/whl/jinja2-3.1.2-variants.json",
|
||||
yanked: None,
|
||||
},
|
||||
],
|
||||
}
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use std::{borrow::Cow, str::FromStr};
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
|
||||
use uv_pep508::PackageName;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, fmt::Formatter};
|
||||
|
||||
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError};
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use std::{borrow::Cow, str::FromStr};
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
/// A host specification (wildcard, or host, with optional scheme and/or port) for which
|
||||
|
|
|
@ -1,9 +1,32 @@
|
|||
use crate::VariantJson;
|
||||
use uv_distribution_filename::DistFilename;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
/// On an index page, there can be wheels, source distributions and `variant.json` files.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DistOrVariantFilename {
|
||||
use crate::VariantsJson;
|
||||
|
||||
/// On an index page, there can be wheels, source distributions and `variants.json` files.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum IndexEntryFilename {
|
||||
DistFilename(DistFilename),
|
||||
VariantJson(VariantJson),
|
||||
VariantJson(VariantsJson),
|
||||
}
|
||||
|
||||
impl IndexEntryFilename {
|
||||
pub fn name(&self) -> &PackageName {
|
||||
match self {
|
||||
Self::DistFilename(filename) => filename.name(),
|
||||
Self::VariantJson(variant_json) => &variant_json.name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a filename as either a distribution filename or a `variants.json` filename.
|
||||
#[allow(clippy::manual_map)]
|
||||
pub fn try_from_normalized_filename(filename: &str) -> Option<Self> {
|
||||
if let Some(dist_filename) = DistFilename::try_from_normalized_filename(filename) {
|
||||
Some(Self::DistFilename(dist_filename))
|
||||
} else if let Some(variant_json) = VariantsJson::try_from_normalized_filename(filename) {
|
||||
Some(Self::VariantJson(variant_json))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
//! flags set.
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::{borrow::Cow, path::Path};
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{Index, IndexUrl};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use uv_variants::VariantPriority;
|
|||
|
||||
use crate::{
|
||||
File, InstalledDist, KnownPlatform, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist,
|
||||
ResolvedDistRef, VariantJson,
|
||||
ResolvedDistRef, VariantsJson,
|
||||
};
|
||||
|
||||
/// A collection of distributions that have been filtered by relevance.
|
||||
|
@ -31,7 +31,7 @@ struct PrioritizedDistInner {
|
|||
/// The set of all wheels associated with this distribution.
|
||||
wheels: Vec<(RegistryBuiltWheel, WheelCompatibility)>,
|
||||
/// The `variants.json` file associated with the package version.
|
||||
variants_json: Option<VariantJson>,
|
||||
variants_json: Option<VariantsJson>,
|
||||
/// The hashes for each distribution.
|
||||
hashes: Vec<HashDigest>,
|
||||
/// The set of supported platforms for the distribution, described in terms of their markers.
|
||||
|
@ -375,8 +375,8 @@ impl PrioritizedDist {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Create a new [`PrioritizedDist`] from the `variant.json`.
|
||||
pub fn from_variant_json(variant_json: VariantJson) -> Self {
|
||||
/// Create a new [`PrioritizedDist`] from the `variants.json`.
|
||||
pub fn from_variant_json(variant_json: VariantsJson) -> Self {
|
||||
Self(Box::new(PrioritizedDistInner {
|
||||
markers: MarkerTree::TRUE,
|
||||
best_wheel_index: None,
|
||||
|
@ -440,10 +440,10 @@ impl PrioritizedDist {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert_variant_json(&mut self, variant_json: VariantJson) {
|
||||
pub fn insert_variant_json(&mut self, variant_json: VariantsJson) {
|
||||
debug_assert!(
|
||||
self.0.variants_json.is_none(),
|
||||
"The variant.json filename is unique"
|
||||
"The variants.json filename is unique"
|
||||
);
|
||||
self.0.variants_json = Some(variant_json);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use std::{borrow::Cow, ops::Deref};
|
||||
#[cfg(feature = "schemars")]
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
|
||||
use http::StatusCode;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
|
|
@ -1,11 +1,39 @@
|
|||
use crate::FileLocation;
|
||||
use std::str::FromStr;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
|
||||
/// A `<name>-<version>-variant.json` file.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VariantJson {
|
||||
/// A `<name>-<version>-variants.json` file.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct VariantsJson {
|
||||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
pub file_location: FileLocation,
|
||||
}
|
||||
|
||||
impl VariantsJson {
|
||||
/// Parse a `<name>-<version>-variants.json` filename.
|
||||
///
|
||||
/// name and version must be normalized, i.e., they don't contain dashes.
|
||||
pub fn try_from_normalized_filename(filename: &str) -> Option<Self> {
|
||||
let stem = filename.strip_suffix("-variants.json")?;
|
||||
|
||||
let (name, version) = stem.split_once('-')?;
|
||||
let name = PackageName::from_str(name).ok()?;
|
||||
let version = Version::from_str(version).ok()?;
|
||||
|
||||
Some(VariantsJson { name, version })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variants_json_parsing() {
|
||||
let variant =
|
||||
VariantsJson::try_from_normalized_filename("numpy-1.21.0-variants.json").unwrap();
|
||||
assert_eq!(variant.name.as_str(), "numpy");
|
||||
assert_eq!(variant.version.to_string(), "1.21.0");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use uv_client::{FlatIndexEntries, FlatIndexEntry};
|
|||
use uv_configuration::BuildOptions;
|
||||
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
|
||||
use uv_distribution_types::{
|
||||
DistOrVariantFilename, File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel,
|
||||
File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexEntryFilename,
|
||||
IndexUrl, PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility,
|
||||
WheelCompatibility,
|
||||
};
|
||||
|
@ -46,7 +46,7 @@ impl FlatIndex {
|
|||
let distributions = index.entry(entry.filename.name().clone()).or_default();
|
||||
distributions.add_file(
|
||||
entry.file,
|
||||
DistOrVariantFilename::DistFilename(entry.filename),
|
||||
entry.filename,
|
||||
tags,
|
||||
variants,
|
||||
hasher,
|
||||
|
@ -92,7 +92,7 @@ impl FlatDistributions {
|
|||
for entry in entries {
|
||||
distributions.add_file(
|
||||
entry.file,
|
||||
DistOrVariantFilename::DistFilename(entry.filename),
|
||||
entry.filename,
|
||||
tags,
|
||||
variants,
|
||||
hasher,
|
||||
|
@ -117,7 +117,7 @@ impl FlatDistributions {
|
|||
fn add_file(
|
||||
&mut self,
|
||||
file: File,
|
||||
filename: DistOrVariantFilename,
|
||||
filename: IndexEntryFilename,
|
||||
tags: Option<&Tags>,
|
||||
variants: Option<&VariantSet>,
|
||||
hasher: &HashStrategy,
|
||||
|
@ -127,7 +127,7 @@ impl FlatDistributions {
|
|||
// No `requires-python` here: for source distributions, we don't have that information;
|
||||
// for wheels, we read it lazily only when selected.
|
||||
match filename {
|
||||
DistOrVariantFilename::DistFilename(DistFilename::WheelFilename(filename)) => {
|
||||
IndexEntryFilename::DistFilename(DistFilename::WheelFilename(filename)) => {
|
||||
let version = filename.version.clone();
|
||||
|
||||
let compatibility = Self::wheel_compatibility(
|
||||
|
@ -152,7 +152,7 @@ impl FlatDistributions {
|
|||
}
|
||||
}
|
||||
}
|
||||
DistOrVariantFilename::DistFilename(DistFilename::SourceDistFilename(filename)) => {
|
||||
IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(filename)) => {
|
||||
let compatibility = Self::source_dist_compatibility(
|
||||
&filename,
|
||||
file.hashes.as_slice(),
|
||||
|
@ -176,7 +176,7 @@ impl FlatDistributions {
|
|||
}
|
||||
}
|
||||
}
|
||||
DistOrVariantFilename::VariantJson(variant_json) => {
|
||||
IndexEntryFilename::VariantJson(variant_json) => {
|
||||
match self.0.entry(variant_json.version.clone()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().insert_variant_json(variant_json);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue