mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 13:14:41 +00:00
Add support for Metadata 2.2 (#2293)
## Summary PyPI now supports Metadata 2.2, which means distributions with Metadata 2.2-compliant metadata will start to appear. The upside is that if a source distribution includes a `PKG-INFO` file with (1) a metadata version of 2.2 or greater, and (2) no dynamic fields (at least, of the fields we rely on), we can read the metadata from the `PKG-INFO` file directly rather than running _any_ of the PEP 517 build hooks. Closes https://github.com/astral-sh/uv/issues/2009.
This commit is contained in:
parent
41c911fc41
commit
2e9678e5d3
14 changed files with 263 additions and 70 deletions
|
|
@ -111,10 +111,10 @@ impl InstalledDist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the `METADATA` file from a `.dist-info` directory.
|
/// Read the `METADATA` file from a `.dist-info` directory.
|
||||||
pub fn metadata(&self) -> Result<pypi_types::Metadata21> {
|
pub fn metadata(&self) -> Result<pypi_types::Metadata23> {
|
||||||
let path = self.path().join("METADATA");
|
let path = self.path().join("METADATA");
|
||||||
let contents = fs::read(&path)?;
|
let contents = fs::read(&path)?;
|
||||||
pypi_types::Metadata21::parse(&contents).with_context(|| {
|
pypi_types::Metadata23::parse_metadata(&contents).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to parse METADATA file at: {}",
|
"Failed to parse METADATA file at: {}",
|
||||||
path.simplified_display()
|
path.simplified_display()
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,16 @@ use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||||
use crate::lenient_requirement::LenientRequirement;
|
use crate::lenient_requirement::LenientRequirement;
|
||||||
use crate::LenientVersionSpecifiers;
|
use crate::LenientVersionSpecifiers;
|
||||||
|
|
||||||
/// Python Package Metadata 2.1 as specified in
|
/// Python Package Metadata 2.3 as specified in
|
||||||
/// <https://packaging.python.org/specifications/core-metadata/>.
|
/// <https://packaging.python.org/specifications/core-metadata/>.
|
||||||
///
|
///
|
||||||
/// This is a subset of the full metadata specification, and only includes the
|
/// This is a subset of the full metadata specification, and only includes the
|
||||||
/// fields that are relevant to dependency resolution.
|
/// fields that are relevant to dependency resolution.
|
||||||
|
///
|
||||||
|
/// At present, we support up to version 2.3 of the metadata specification.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct Metadata21 {
|
pub struct Metadata23 {
|
||||||
// Mandatory fields
|
// Mandatory fields
|
||||||
pub metadata_version: String,
|
pub metadata_version: String,
|
||||||
pub name: PackageName,
|
pub name: PackageName,
|
||||||
|
|
@ -70,12 +72,18 @@ pub enum Error {
|
||||||
Pep508Error(#[from] Pep508Error),
|
Pep508Error(#[from] Pep508Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidName(#[from] InvalidNameError),
|
InvalidName(#[from] InvalidNameError),
|
||||||
|
#[error("Invalid `Metadata-Version` field: {0}")]
|
||||||
|
InvalidMetadataVersion(String),
|
||||||
|
#[error("Reading metadata from `PKG-INFO` requires Metadata 2.2 or later (found: {0})")]
|
||||||
|
UnsupportedMetadataVersion(String),
|
||||||
|
#[error("The following field was marked as dynamic: {0}")]
|
||||||
|
DynamicField(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// From <https://github.com/PyO3/python-pkginfo-rs/blob/d719988323a0cfea86d4737116d7917f30e819e2/src/metadata.rs#LL78C2-L91C26>
|
/// From <https://github.com/PyO3/python-pkginfo-rs/blob/d719988323a0cfea86d4737116d7917f30e819e2/src/metadata.rs#LL78C2-L91C26>
|
||||||
impl Metadata21 {
|
impl Metadata23 {
|
||||||
/// Parse distribution metadata from metadata bytes
|
/// Parse the [`Metadata23`] from a `METADATA` file, as included in a built distribution (wheel).
|
||||||
pub fn parse(content: &[u8]) -> Result<Self, Error> {
|
pub fn parse_metadata(content: &[u8]) -> Result<Self, Error> {
|
||||||
let headers = Headers::parse(content)?;
|
let headers = Headers::parse(content)?;
|
||||||
|
|
||||||
let metadata_version = headers
|
let metadata_version = headers
|
||||||
|
|
@ -124,13 +132,96 @@ impl Metadata21 {
|
||||||
provides_extras,
|
provides_extras,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the [`Metadata23`] from a source distribution's `PKG-INFO` file, if it uses Metadata 2.2
|
||||||
|
/// or later _and_ none of the required fields (`Requires-Python`, `Requires-Dist`, and
|
||||||
|
/// `Provides-Extra`) are marked as dynamic.
|
||||||
|
pub fn parse_pkg_info(content: &[u8]) -> Result<Self, Error> {
|
||||||
|
let headers = Headers::parse(content)?;
|
||||||
|
|
||||||
|
// To rely on a source distribution's `PKG-INFO` file, the `Metadata-Version` field must be
|
||||||
|
// present and set to a value of at least `2.2`.
|
||||||
|
let metadata_version = headers
|
||||||
|
.get_first_value("Metadata-Version")
|
||||||
|
.ok_or(Error::FieldNotFound("Metadata-Version"))?;
|
||||||
|
|
||||||
|
// Parse the version into (major, minor).
|
||||||
|
let (major, minor) = parse_version(&metadata_version)?;
|
||||||
|
if (major, minor) < (2, 2) || (major, minor) >= (3, 0) {
|
||||||
|
return Err(Error::UnsupportedMetadataVersion(metadata_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the fields we need are marked as dynamic, we can't use the `PKG-INFO` file.
|
||||||
|
let dynamic = headers.get_all_values("Dynamic").collect::<Vec<_>>();
|
||||||
|
for field in dynamic {
|
||||||
|
match field.as_str() {
|
||||||
|
"Requires-Python" => return Err(Error::DynamicField("Requires-Python")),
|
||||||
|
"Requires-Dist" => return Err(Error::DynamicField("Requires-Dist")),
|
||||||
|
"Provides-Extra" => return Err(Error::DynamicField("Provides-Extra")),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `Name` and `Version` fields are required, and can't be dynamic.
|
||||||
|
let name = PackageName::new(
|
||||||
|
headers
|
||||||
|
.get_first_value("Name")
|
||||||
|
.ok_or(Error::FieldNotFound("Name"))?,
|
||||||
|
)?;
|
||||||
|
let version = Version::from_str(
|
||||||
|
&headers
|
||||||
|
.get_first_value("Version")
|
||||||
|
.ok_or(Error::FieldNotFound("Version"))?,
|
||||||
|
)
|
||||||
|
.map_err(Error::Pep440VersionError)?;
|
||||||
|
|
||||||
|
// The remaining fields are required to be present.
|
||||||
|
let requires_dist = headers
|
||||||
|
.get_all_values("Requires-Dist")
|
||||||
|
.map(|requires_dist| {
|
||||||
|
LenientRequirement::from_str(&requires_dist).map(Requirement::from)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let requires_python = headers
|
||||||
|
.get_first_value("Requires-Python")
|
||||||
|
.map(|requires_python| {
|
||||||
|
LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let provides_extras = headers
|
||||||
|
.get_all_values("Provides-Extra")
|
||||||
|
.filter_map(|provides_extra| match ExtraName::new(provides_extra) {
|
||||||
|
Ok(extra_name) => Some(extra_name),
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Ignoring invalid extra: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
metadata_version,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
requires_dist,
|
||||||
|
requires_python,
|
||||||
|
provides_extras,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Metadata21 {
|
/// Parse a `Metadata-Version` field into a (major, minor) tuple.
|
||||||
type Err = Error;
|
fn parse_version(metadata_version: &str) -> Result<(u8, u8), Error> {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
let (major, minor) = metadata_version
|
||||||
Self::parse(s.as_bytes())
|
.split_once('.')
|
||||||
}
|
.ok_or(Error::InvalidMetadataVersion(metadata_version.to_string()))?;
|
||||||
|
let major = major
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?;
|
||||||
|
let minor = minor
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| Error::InvalidMetadataVersion(metadata_version.to_string()))?;
|
||||||
|
Ok((major, minor))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The headers of a distribution metadata file.
|
/// The headers of a distribution metadata file.
|
||||||
|
|
@ -174,38 +265,70 @@ mod tests {
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
use super::Metadata21;
|
use super::Metadata23;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_from_str() {
|
fn test_parse_metadata() {
|
||||||
let s = "Metadata-Version: 1.0";
|
let s = "Metadata-Version: 1.0";
|
||||||
let meta: Result<Metadata21, Error> = s.parse();
|
let meta = Metadata23::parse_metadata(s.as_bytes());
|
||||||
assert!(matches!(meta, Err(Error::FieldNotFound("Name"))));
|
assert!(matches!(meta, Err(Error::FieldNotFound("Name"))));
|
||||||
|
|
||||||
let s = "Metadata-Version: 1.0\nName: asdf";
|
let s = "Metadata-Version: 1.0\nName: asdf";
|
||||||
let meta = Metadata21::parse(s.as_bytes());
|
let meta = Metadata23::parse_metadata(s.as_bytes());
|
||||||
assert!(matches!(meta, Err(Error::FieldNotFound("Version"))));
|
assert!(matches!(meta, Err(Error::FieldNotFound("Version"))));
|
||||||
|
|
||||||
let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0";
|
let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0";
|
||||||
let meta = Metadata21::parse(s.as_bytes()).unwrap();
|
let meta = Metadata23::parse_metadata(s.as_bytes()).unwrap();
|
||||||
assert_eq!(meta.metadata_version, "1.0");
|
assert_eq!(meta.metadata_version, "1.0");
|
||||||
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
||||||
assert_eq!(meta.version, Version::new([1, 0]));
|
assert_eq!(meta.version, Version::new([1, 0]));
|
||||||
|
|
||||||
let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包";
|
let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包";
|
||||||
let meta = Metadata21::parse(s.as_bytes()).unwrap();
|
let meta = Metadata23::parse_metadata(s.as_bytes()).unwrap();
|
||||||
assert_eq!(meta.metadata_version, "1.0");
|
assert_eq!(meta.metadata_version, "1.0");
|
||||||
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
||||||
assert_eq!(meta.version, Version::new([1, 0]));
|
assert_eq!(meta.version, Version::new([1, 0]));
|
||||||
|
|
||||||
let s = "Metadata-Version: 1.0\nName: =?utf-8?q?foobar?=\nVersion: 1.0";
|
let s = "Metadata-Version: 1.0\nName: =?utf-8?q?foobar?=\nVersion: 1.0";
|
||||||
let meta = Metadata21::parse(s.as_bytes()).unwrap();
|
let meta = Metadata23::parse_metadata(s.as_bytes()).unwrap();
|
||||||
assert_eq!(meta.metadata_version, "1.0");
|
assert_eq!(meta.metadata_version, "1.0");
|
||||||
assert_eq!(meta.name, PackageName::from_str("foobar").unwrap());
|
assert_eq!(meta.name, PackageName::from_str("foobar").unwrap());
|
||||||
assert_eq!(meta.version, Version::new([1, 0]));
|
assert_eq!(meta.version, Version::new([1, 0]));
|
||||||
|
|
||||||
let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= <x@y.org>\nVersion: 1.0";
|
let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= <x@y.org>\nVersion: 1.0";
|
||||||
let meta = Metadata21::parse(s.as_bytes());
|
let meta = Metadata23::parse_metadata(s.as_bytes());
|
||||||
assert!(matches!(meta, Err(Error::InvalidName(_))));
|
assert!(matches!(meta, Err(Error::InvalidName(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_pkg_info() {
|
||||||
|
let s = "Metadata-Version: 2.1";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes());
|
||||||
|
assert!(matches!(meta, Err(Error::UnsupportedMetadataVersion(_))));
|
||||||
|
|
||||||
|
let s = "Metadata-Version: 2.2\nName: asdf";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes());
|
||||||
|
assert!(matches!(meta, Err(Error::FieldNotFound("Version"))));
|
||||||
|
|
||||||
|
let s = "Metadata-Version: 2.3\nName: asdf";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes());
|
||||||
|
assert!(matches!(meta, Err(Error::FieldNotFound("Version"))));
|
||||||
|
|
||||||
|
let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap();
|
||||||
|
assert_eq!(meta.metadata_version, "2.3");
|
||||||
|
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
||||||
|
assert_eq!(meta.version, Version::new([1, 0]));
|
||||||
|
|
||||||
|
let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nDynamic: Requires-Dist";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap_err();
|
||||||
|
assert!(matches!(meta, Error::DynamicField("Requires-Dist")));
|
||||||
|
|
||||||
|
let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nRequires-Dist: foo";
|
||||||
|
let meta = Metadata23::parse_pkg_info(s.as_bytes()).unwrap();
|
||||||
|
assert_eq!(meta.metadata_version, "2.3");
|
||||||
|
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
|
||||||
|
assert_eq!(meta.version, Version::new([1, 0]));
|
||||||
|
assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
|
||||||
use distribution_types::{BuiltDist, File, FileLocation, IndexUrl, IndexUrls, Name};
|
use distribution_types::{BuiltDist, File, FileLocation, IndexUrl, IndexUrls, Name};
|
||||||
use install_wheel_rs::{find_dist_info, is_metadata_entry};
|
use install_wheel_rs::{find_dist_info, is_metadata_entry};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pypi_types::{Metadata21, SimpleJson};
|
use pypi_types::{Metadata23, SimpleJson};
|
||||||
use uv_auth::safe_copy_url_auth;
|
use uv_auth::safe_copy_url_auth;
|
||||||
use uv_cache::{Cache, CacheBucket, WheelCache};
|
use uv_cache::{Cache, CacheBucket, WheelCache};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -343,7 +343,7 @@ impl RegistryClient {
|
||||||
/// 2. From a remote wheel by partial zip reading
|
/// 2. From a remote wheel by partial zip reading
|
||||||
/// 3. From a (temp) download of a remote wheel (this is a fallback, the webserver should support range requests)
|
/// 3. From a (temp) download of a remote wheel (this is a fallback, the webserver should support range requests)
|
||||||
#[instrument(skip_all, fields(% built_dist))]
|
#[instrument(skip_all, fields(% built_dist))]
|
||||||
pub async fn wheel_metadata(&self, built_dist: &BuiltDist) -> Result<Metadata21, Error> {
|
pub async fn wheel_metadata(&self, built_dist: &BuiltDist) -> Result<Metadata23, Error> {
|
||||||
let metadata = match &built_dist {
|
let metadata = match &built_dist {
|
||||||
BuiltDist::Registry(wheel) => match &wheel.file.url {
|
BuiltDist::Registry(wheel) => match &wheel.file.url {
|
||||||
FileLocation::RelativeUrl(base, url) => {
|
FileLocation::RelativeUrl(base, url) => {
|
||||||
|
|
@ -399,7 +399,7 @@ impl RegistryClient {
|
||||||
index: &IndexUrl,
|
index: &IndexUrl,
|
||||||
file: &File,
|
file: &File,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
// If the metadata file is available at its own url (PEP 658), download it from there.
|
// If the metadata file is available at its own url (PEP 658), download it from there.
|
||||||
let filename = WheelFilename::from_str(&file.filename).map_err(ErrorKind::WheelFilename)?;
|
let filename = WheelFilename::from_str(&file.filename).map_err(ErrorKind::WheelFilename)?;
|
||||||
if file
|
if file
|
||||||
|
|
@ -428,7 +428,7 @@ impl RegistryClient {
|
||||||
let bytes = response.bytes().await.map_err(ErrorKind::from)?;
|
let bytes = response.bytes().await.map_err(ErrorKind::from)?;
|
||||||
|
|
||||||
info_span!("parse_metadata21")
|
info_span!("parse_metadata21")
|
||||||
.in_scope(|| Metadata21::parse(bytes.as_ref()))
|
.in_scope(|| Metadata23::parse_metadata(bytes.as_ref()))
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
Error::from(ErrorKind::MetadataParseError(
|
Error::from(ErrorKind::MetadataParseError(
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -462,7 +462,7 @@ impl RegistryClient {
|
||||||
filename: &'data WheelFilename,
|
filename: &'data WheelFilename,
|
||||||
url: &'data Url,
|
url: &'data Url,
|
||||||
cache_shard: WheelCache<'data>,
|
cache_shard: WheelCache<'data>,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let cache_entry = self.cache.entry(
|
let cache_entry = self.cache.entry(
|
||||||
CacheBucket::Wheels,
|
CacheBucket::Wheels,
|
||||||
cache_shard.remote_wheel_dir(filename.name.as_ref()),
|
cache_shard.remote_wheel_dir(filename.name.as_ref()),
|
||||||
|
|
@ -507,14 +507,14 @@ impl RegistryClient {
|
||||||
.map_err(ErrorKind::AsyncHttpRangeReader)?;
|
.map_err(ErrorKind::AsyncHttpRangeReader)?;
|
||||||
trace!("Getting metadata for {filename} by range request");
|
trace!("Getting metadata for {filename} by range request");
|
||||||
let text = wheel_metadata_from_remote_zip(filename, &mut reader).await?;
|
let text = wheel_metadata_from_remote_zip(filename, &mut reader).await?;
|
||||||
let metadata = Metadata21::parse(text.as_bytes()).map_err(|err| {
|
let metadata = Metadata23::parse_metadata(text.as_bytes()).map_err(|err| {
|
||||||
Error::from(ErrorKind::MetadataParseError(
|
Error::from(ErrorKind::MetadataParseError(
|
||||||
filename.clone(),
|
filename.clone(),
|
||||||
url.to_string(),
|
url.to_string(),
|
||||||
Box::new(err),
|
Box::new(err),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
Ok::<Metadata21, CachedClientError<Error>>(metadata)
|
Ok::<Metadata23, CachedClientError<Error>>(metadata)
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
.instrument(info_span!("read_metadata_range_request", wheel = %filename))
|
.instrument(info_span!("read_metadata_range_request", wheel = %filename))
|
||||||
|
|
@ -591,7 +591,7 @@ async fn read_metadata_async_seek(
|
||||||
filename: &WheelFilename,
|
filename: &WheelFilename,
|
||||||
debug_source: String,
|
debug_source: String,
|
||||||
reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin,
|
reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let mut zip_reader = async_zip::tokio::read::seek::ZipFileReader::with_tokio(reader)
|
let mut zip_reader = async_zip::tokio::read::seek::ZipFileReader::with_tokio(reader)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ErrorKind::Zip(filename.clone(), err))?;
|
.map_err(|err| ErrorKind::Zip(filename.clone(), err))?;
|
||||||
|
|
@ -617,7 +617,7 @@ async fn read_metadata_async_seek(
|
||||||
.await
|
.await
|
||||||
.map_err(|err| ErrorKind::Zip(filename.clone(), err))?;
|
.map_err(|err| ErrorKind::Zip(filename.clone(), err))?;
|
||||||
|
|
||||||
let metadata = Metadata21::parse(&contents).map_err(|err| {
|
let metadata = Metadata23::parse_metadata(&contents).map_err(|err| {
|
||||||
ErrorKind::MetadataParseError(filename.clone(), debug_source, Box::new(err))
|
ErrorKind::MetadataParseError(filename.clone(), debug_source, Box::new(err))
|
||||||
})?;
|
})?;
|
||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
|
|
@ -628,7 +628,7 @@ async fn read_metadata_async_stream<R: futures::AsyncRead + Unpin>(
|
||||||
filename: &WheelFilename,
|
filename: &WheelFilename,
|
||||||
debug_source: String,
|
debug_source: String,
|
||||||
reader: R,
|
reader: R,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let mut zip = async_zip::base::read::stream::ZipFileReader::new(reader);
|
let mut zip = async_zip::base::read::stream::ZipFileReader::new(reader);
|
||||||
|
|
||||||
while let Some(mut entry) = zip
|
while let Some(mut entry) = zip
|
||||||
|
|
@ -649,7 +649,7 @@ async fn read_metadata_async_stream<R: futures::AsyncRead + Unpin>(
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
reader.read_to_end(&mut contents).await.unwrap();
|
reader.read_to_end(&mut contents).await.unwrap();
|
||||||
|
|
||||||
let metadata = Metadata21::parse(&contents).map_err(|err| {
|
let metadata = Metadata23::parse_metadata(&contents).map_err(|err| {
|
||||||
ErrorKind::MetadataParseError(filename.clone(), debug_source, Box::new(err))
|
ErrorKind::MetadataParseError(filename.clone(), debug_source, Box::new(err))
|
||||||
})?;
|
})?;
|
||||||
return Ok(metadata);
|
return Ok(metadata);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use distribution_types::{
|
||||||
BuiltDist, DirectGitUrl, Dist, FileLocation, IndexLocations, LocalEditable, Name, SourceDist,
|
BuiltDist, DirectGitUrl, Dist, FileLocation, IndexLocations, LocalEditable, Name, SourceDist,
|
||||||
};
|
};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
||||||
use uv_client::{CacheControl, CachedClientError, Connectivity, RegistryClient};
|
use uv_client::{CacheControl, CachedClientError, Connectivity, RegistryClient};
|
||||||
|
|
||||||
|
|
@ -344,14 +344,14 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context>
|
||||||
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
|
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
|
||||||
/// fetch and build the source distribution.
|
/// fetch and build the source distribution.
|
||||||
///
|
///
|
||||||
/// Returns the [`Metadata21`], along with a "precise" URL for the source distribution, if
|
/// Returns the [`Metadata23`], along with a "precise" URL for the source distribution, if
|
||||||
/// possible. For example, given a Git dependency with a reference to a branch or tag, return a
|
/// possible. For example, given a Git dependency with a reference to a branch or tag, return a
|
||||||
/// URL with a precise reference to the current commit of that branch or tag.
|
/// URL with a precise reference to the current commit of that branch or tag.
|
||||||
#[instrument(skip_all, fields(%dist))]
|
#[instrument(skip_all, fields(%dist))]
|
||||||
pub async fn get_or_build_wheel_metadata(
|
pub async fn get_or_build_wheel_metadata(
|
||||||
&self,
|
&self,
|
||||||
dist: &Dist,
|
dist: &Dist,
|
||||||
) -> Result<(Metadata21, Option<Url>), Error> {
|
) -> Result<(Metadata23, Option<Url>), Error> {
|
||||||
match dist {
|
match dist {
|
||||||
Dist::Built(built_dist) => {
|
Dist::Built(built_dist) => {
|
||||||
Ok((self.client.wheel_metadata(built_dist).boxed().await?, None))
|
Ok((self.client.wheel_metadata(built_dist).boxed().await?, None))
|
||||||
|
|
@ -393,7 +393,7 @@ impl<'a, Context: BuildContext + Send + Sync> DistributionDatabase<'a, Context>
|
||||||
&self,
|
&self,
|
||||||
editable: &LocalEditable,
|
editable: &LocalEditable,
|
||||||
editable_wheel_dir: &Path,
|
editable_wheel_dir: &Path,
|
||||||
) -> Result<(LocalWheel, Metadata21), Error> {
|
) -> Result<(LocalWheel, Metadata23), Error> {
|
||||||
let (dist, disk_filename, filename, metadata) = self
|
let (dist, disk_filename, filename, metadata) = self
|
||||||
.builder
|
.builder
|
||||||
.build_editable(editable, editable_wheel_dir)
|
.build_editable(editable, editable_wheel_dir)
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ pub enum Error {
|
||||||
Extract(#[from] uv_extract::Error),
|
Extract(#[from] uv_extract::Error),
|
||||||
#[error("Source distribution not found at: {0}")]
|
#[error("Source distribution not found at: {0}")]
|
||||||
NotFound(PathBuf),
|
NotFound(PathBuf),
|
||||||
|
#[error("The source distribution is missing a `PKG-INFO` file")]
|
||||||
|
MissingPkgInfo,
|
||||||
|
#[error("The source distribution does not support static metadata")]
|
||||||
|
DynamicPkgInfo(#[source] pypi_types::Error),
|
||||||
|
|
||||||
/// Should not occur; only seen when another task panicked.
|
/// Should not occur; only seen when another task panicked.
|
||||||
#[error("The task executor is broken, did some other task panic?")]
|
#[error("The task executor is broken, did some other task panic?")]
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use distribution_types::{
|
||||||
use install_wheel_rs::read_dist_info;
|
use install_wheel_rs::read_dist_info;
|
||||||
use pep508_rs::VerbatimUrl;
|
use pep508_rs::VerbatimUrl;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_cache::{
|
use uv_cache::{
|
||||||
ArchiveTimestamp, CacheBucket, CacheEntry, CacheShard, CachedByTimestamp, Freshness, WheelCache,
|
ArchiveTimestamp, CacheBucket, CacheEntry, CacheShard, CachedByTimestamp, Freshness, WheelCache,
|
||||||
};
|
};
|
||||||
|
|
@ -174,7 +174,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
pub async fn download_and_build_metadata(
|
pub async fn download_and_build_metadata(
|
||||||
&self,
|
&self,
|
||||||
source_dist: &SourceDist,
|
source_dist: &SourceDist,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let metadata = match &source_dist {
|
let metadata = match &source_dist {
|
||||||
SourceDist::DirectUrl(direct_url_source_dist) => {
|
SourceDist::DirectUrl(direct_url_source_dist) => {
|
||||||
let filename = direct_url_source_dist
|
let filename = direct_url_source_dist
|
||||||
|
|
@ -376,7 +376,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
url: &'data Url,
|
url: &'data Url,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
subdirectory: Option<&'data Path>,
|
subdirectory: Option<&'data Path>,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let cache_entry = cache_shard.entry(MANIFEST);
|
let cache_entry = cache_shard.entry(MANIFEST);
|
||||||
let cache_control = match self.client.connectivity() {
|
let cache_control = match self.client.connectivity() {
|
||||||
Connectivity::Online => CacheControl::from(
|
Connectivity::Online => CacheControl::from(
|
||||||
|
|
@ -564,7 +564,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
source_dist: &SourceDist,
|
source_dist: &SourceDist,
|
||||||
path_source_dist: &PathSourceDist,
|
path_source_dist: &PathSourceDist,
|
||||||
source_root: &Path,
|
source_root: &Path,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let cache_shard = self.build_context.cache().shard(
|
let cache_shard = self.build_context.cache().shard(
|
||||||
CacheBucket::BuiltWheels,
|
CacheBucket::BuiltWheels,
|
||||||
WheelCache::Path(&path_source_dist.url)
|
WheelCache::Path(&path_source_dist.url)
|
||||||
|
|
@ -712,7 +712,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
&self,
|
&self,
|
||||||
source_dist: &SourceDist,
|
source_dist: &SourceDist,
|
||||||
git_source_dist: &GitSourceDist,
|
git_source_dist: &GitSourceDist,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let (fetch, subdirectory) = self.download_source_dist_git(&git_source_dist.url).await?;
|
let (fetch, subdirectory) = self.download_source_dist_git(&git_source_dist.url).await?;
|
||||||
|
|
||||||
let git_sha = fetch.git().precise().expect("Exact commit after checkout");
|
let git_sha = fetch.git().precise().expect("Exact commit after checkout");
|
||||||
|
|
@ -913,7 +913,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
source_dist: &Path,
|
source_dist: &Path,
|
||||||
subdirectory: Option<&Path>,
|
subdirectory: Option<&Path>,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
) -> Result<(String, WheelFilename, Metadata21), Error> {
|
) -> Result<(String, WheelFilename, Metadata23), Error> {
|
||||||
debug!("Building: {dist}");
|
debug!("Building: {dist}");
|
||||||
|
|
||||||
// Guard against build of source distributions when disabled
|
// Guard against build of source distributions when disabled
|
||||||
|
|
@ -966,16 +966,37 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
async fn build_source_dist_metadata(
|
async fn build_source_dist_metadata(
|
||||||
&self,
|
&self,
|
||||||
dist: &SourceDist,
|
dist: &SourceDist,
|
||||||
source_dist: &Path,
|
source_tree: &Path,
|
||||||
subdirectory: Option<&Path>,
|
subdirectory: Option<&Path>,
|
||||||
) -> Result<Option<Metadata21>, Error> {
|
) -> Result<Option<Metadata23>, Error> {
|
||||||
debug!("Preparing metadata for: {dist}");
|
debug!("Preparing metadata for: {dist}");
|
||||||
|
|
||||||
|
// Attempt to read static metadata from the source distribution.
|
||||||
|
match read_pkg_info(source_tree).await {
|
||||||
|
Ok(metadata) => {
|
||||||
|
debug!("Found static metadata for: {dist}");
|
||||||
|
|
||||||
|
// Validate the metadata.
|
||||||
|
if &metadata.name != dist.name() {
|
||||||
|
return Err(Error::NameMismatch {
|
||||||
|
metadata: metadata.name,
|
||||||
|
given: dist.name().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(metadata));
|
||||||
|
}
|
||||||
|
Err(err @ (Error::MissingPkgInfo | Error::DynamicPkgInfo(_))) => {
|
||||||
|
debug!("No static metadata available for: {dist} ({err:?})");
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the builder.
|
// Setup the builder.
|
||||||
let mut builder = self
|
let mut builder = self
|
||||||
.build_context
|
.build_context
|
||||||
.setup_build(
|
.setup_build(
|
||||||
source_dist,
|
source_tree,
|
||||||
subdirectory,
|
subdirectory,
|
||||||
&dist.to_string(),
|
&dist.to_string(),
|
||||||
Some(dist),
|
Some(dist),
|
||||||
|
|
@ -998,7 +1019,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
let content = fs::read(dist_info.join("METADATA"))
|
let content = fs::read(dist_info.join("METADATA"))
|
||||||
.await
|
.await
|
||||||
.map_err(Error::CacheRead)?;
|
.map_err(Error::CacheRead)?;
|
||||||
let metadata = Metadata21::parse(&content)?;
|
let metadata = Metadata23::parse_metadata(&content)?;
|
||||||
|
|
||||||
// Validate the metadata.
|
// Validate the metadata.
|
||||||
if &metadata.name != dist.name() {
|
if &metadata.name != dist.name() {
|
||||||
|
|
@ -1016,7 +1037,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
|
||||||
&self,
|
&self,
|
||||||
editable: &LocalEditable,
|
editable: &LocalEditable,
|
||||||
editable_wheel_dir: &Path,
|
editable_wheel_dir: &Path,
|
||||||
) -> Result<(Dist, String, WheelFilename, Metadata21), Error> {
|
) -> Result<(Dist, String, WheelFilename, Metadata23), Error> {
|
||||||
debug!("Building (editable) {editable}");
|
debug!("Building (editable) {editable}");
|
||||||
|
|
||||||
// Verify that the editable exists.
|
// Verify that the editable exists.
|
||||||
|
|
@ -1074,6 +1095,25 @@ impl ExtractedSource<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the [`Metadata23`] from a source distribution's `PKG-INFO` file, if it uses Metadata 2.2
|
||||||
|
/// or later _and_ none of the required fields (`Requires-Python`, `Requires-Dist`, and
|
||||||
|
/// `Provides-Extra`) are marked as dynamic.
|
||||||
|
pub(crate) async fn read_pkg_info(source_tree: &Path) -> Result<Metadata23, Error> {
|
||||||
|
// Read the `PKG-INFO` file.
|
||||||
|
let content = match fs::read(source_tree.join("PKG-INFO")).await {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Err(Error::MissingPkgInfo);
|
||||||
|
}
|
||||||
|
Err(err) => return Err(Error::CacheRead(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the metadata.
|
||||||
|
let metadata = Metadata23::parse_pkg_info(&content).map_err(Error::DynamicPkgInfo)?;
|
||||||
|
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
/// Read an existing HTTP-cached [`Manifest`], if it exists.
|
/// Read an existing HTTP-cached [`Manifest`], if it exists.
|
||||||
pub(crate) fn read_http_manifest(cache_entry: &CacheEntry) -> Result<Option<Manifest>, Error> {
|
pub(crate) fn read_http_manifest(cache_entry: &CacheEntry) -> Result<Option<Manifest>, Error> {
|
||||||
match fs_err::File::open(cache_entry.path()) {
|
match fs_err::File::open(cache_entry.path()) {
|
||||||
|
|
@ -1139,25 +1179,25 @@ pub(crate) async fn refresh_timestamp_manifest(
|
||||||
Ok(manifest)
|
Ok(manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read an existing cached [`Metadata21`], if it exists.
|
/// Read an existing cached [`Metadata23`], if it exists.
|
||||||
pub(crate) async fn read_cached_metadata(
|
pub(crate) async fn read_cached_metadata(
|
||||||
cache_entry: &CacheEntry,
|
cache_entry: &CacheEntry,
|
||||||
) -> Result<Option<Metadata21>, Error> {
|
) -> Result<Option<Metadata23>, Error> {
|
||||||
match fs::read(&cache_entry.path()).await {
|
match fs::read(&cache_entry.path()).await {
|
||||||
Ok(cached) => Ok(Some(rmp_serde::from_slice::<Metadata21>(&cached)?)),
|
Ok(cached) => Ok(Some(rmp_serde::from_slice::<Metadata23>(&cached)?)),
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||||
Err(err) => Err(Error::CacheRead(err)),
|
Err(err) => Err(Error::CacheRead(err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the [`Metadata21`] from a built wheel.
|
/// Read the [`Metadata23`] from a built wheel.
|
||||||
fn read_wheel_metadata(
|
fn read_wheel_metadata(
|
||||||
filename: &WheelFilename,
|
filename: &WheelFilename,
|
||||||
wheel: impl Into<PathBuf>,
|
wheel: impl Into<PathBuf>,
|
||||||
) -> Result<Metadata21, Error> {
|
) -> Result<Metadata23, Error> {
|
||||||
let file = fs_err::File::open(wheel).map_err(Error::CacheRead)?;
|
let file = fs_err::File::open(wheel).map_err(Error::CacheRead)?;
|
||||||
let reader = std::io::BufReader::new(file);
|
let reader = std::io::BufReader::new(file);
|
||||||
let mut archive = ZipArchive::new(reader)?;
|
let mut archive = ZipArchive::new(reader)?;
|
||||||
let dist_info = read_dist_info(filename, &mut archive)?;
|
let dist_info = read_dist_info(filename, &mut archive)?;
|
||||||
Ok(Metadata21::parse(&dist_info)?)
|
Ok(Metadata23::parse_metadata(&dist_info)?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
CachedDist, InstalledDist, InstalledMetadata, InstalledVersion, LocalEditable, Name,
|
CachedDist, InstalledDist, InstalledMetadata, InstalledVersion, LocalEditable, Name,
|
||||||
};
|
};
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use requirements_txt::EditableRequirement;
|
use requirements_txt::EditableRequirement;
|
||||||
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -14,7 +14,7 @@ use uv_normalize::PackageName;
|
||||||
pub struct BuiltEditable {
|
pub struct BuiltEditable {
|
||||||
pub editable: LocalEditable,
|
pub editable: LocalEditable,
|
||||||
pub wheel: CachedDist,
|
pub wheel: CachedDist,
|
||||||
pub metadata: Metadata21,
|
pub metadata: Metadata23,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An editable distribution that has been resolved to a concrete distribution.
|
/// An editable distribution that has been resolved to a concrete distribution.
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@ use std::hash::BuildHasherDefault;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::LocalEditable;
|
use distribution_types::LocalEditable;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
/// A set of editable packages, indexed by package name.
|
/// A set of editable packages, indexed by package name.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub(crate) struct Editables(FxHashMap<PackageName, (LocalEditable, Metadata21)>);
|
pub(crate) struct Editables(FxHashMap<PackageName, (LocalEditable, Metadata23)>);
|
||||||
|
|
||||||
impl Editables {
|
impl Editables {
|
||||||
/// Create a new set of editables from a set of requirements.
|
/// Create a new set of editables from a set of requirements.
|
||||||
pub(crate) fn from_requirements(requirements: Vec<(LocalEditable, Metadata21)>) -> Self {
|
pub(crate) fn from_requirements(requirements: Vec<(LocalEditable, Metadata23)>) -> Self {
|
||||||
let mut editables =
|
let mut editables =
|
||||||
FxHashMap::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
|
FxHashMap::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
|
||||||
for (editable_requirement, metadata) in requirements {
|
for (editable_requirement, metadata) in requirements {
|
||||||
|
|
@ -22,12 +22,12 @@ impl Editables {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the editable for a package.
|
/// Get the editable for a package.
|
||||||
pub(crate) fn get(&self, name: &PackageName) -> Option<&(LocalEditable, Metadata21)> {
|
pub(crate) fn get(&self, name: &PackageName) -> Option<&(LocalEditable, Metadata23)> {
|
||||||
self.0.get(name)
|
self.0.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all editables.
|
/// Iterate over all editables.
|
||||||
pub(crate) fn iter(&self) -> impl Iterator<Item = &(LocalEditable, Metadata21)> {
|
pub(crate) fn iter(&self) -> impl Iterator<Item = &(LocalEditable, Metadata23)> {
|
||||||
self.0.values()
|
self.0.values()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use distribution_types::LocalEditable;
|
use distribution_types::LocalEditable;
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
/// A manifest of requirements, constraints, and preferences.
|
/// A manifest of requirements, constraints, and preferences.
|
||||||
|
|
@ -11,7 +11,7 @@ pub struct Manifest {
|
||||||
pub(crate) overrides: Vec<Requirement>,
|
pub(crate) overrides: Vec<Requirement>,
|
||||||
pub(crate) preferences: Vec<Requirement>,
|
pub(crate) preferences: Vec<Requirement>,
|
||||||
pub(crate) project: Option<PackageName>,
|
pub(crate) project: Option<PackageName>,
|
||||||
pub(crate) editables: Vec<(LocalEditable, Metadata21)>,
|
pub(crate) editables: Vec<(LocalEditable, Metadata23)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manifest {
|
impl Manifest {
|
||||||
|
|
@ -21,7 +21,7 @@ impl Manifest {
|
||||||
overrides: Vec<Requirement>,
|
overrides: Vec<Requirement>,
|
||||||
preferences: Vec<Requirement>,
|
preferences: Vec<Requirement>,
|
||||||
project: Option<PackageName>,
|
project: Option<PackageName>,
|
||||||
editables: Vec<(LocalEditable, Metadata21)>,
|
editables: Vec<(LocalEditable, Metadata23)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
requirements,
|
requirements,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use url::Url;
|
||||||
use distribution_types::{Dist, DistributionMetadata, LocalEditable, Name, PackageId, Verbatim};
|
use distribution_types::{Dist, DistributionMetadata, LocalEditable, Name, PackageId, Verbatim};
|
||||||
use once_map::OnceMap;
|
use once_map::OnceMap;
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pypi_types::{Hashes, Metadata21};
|
use pypi_types::{Hashes, Metadata23};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
||||||
use crate::editables::Editables;
|
use crate::editables::Editables;
|
||||||
|
|
@ -57,7 +57,7 @@ impl ResolutionGraph {
|
||||||
selection: &SelectedDependencies<PubGrubPackage, Version>,
|
selection: &SelectedDependencies<PubGrubPackage, Version>,
|
||||||
pins: &FilePins,
|
pins: &FilePins,
|
||||||
packages: &OnceMap<PackageName, VersionsResponse>,
|
packages: &OnceMap<PackageName, VersionsResponse>,
|
||||||
distributions: &OnceMap<PackageId, Metadata21>,
|
distributions: &OnceMap<PackageId, Metadata23>,
|
||||||
redirects: &DashMap<Url, Url>,
|
redirects: &DashMap<Url, Url>,
|
||||||
state: &State<PubGrubPackage, Range<Version>, PubGrubPriority>,
|
state: &State<PubGrubPackage, Range<Version>, PubGrubPriority>,
|
||||||
editables: Editables,
|
editables: Editables,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use url::Url;
|
||||||
|
|
||||||
use distribution_types::PackageId;
|
use distribution_types::PackageId;
|
||||||
use once_map::OnceMap;
|
use once_map::OnceMap;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
use super::provider::VersionsResponse;
|
use super::provider::VersionsResponse;
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct InMemoryIndex {
|
||||||
pub(crate) packages: OnceMap<PackageName, VersionsResponse>,
|
pub(crate) packages: OnceMap<PackageName, VersionsResponse>,
|
||||||
|
|
||||||
/// A map from package ID to metadata for that distribution.
|
/// A map from package ID to metadata for that distribution.
|
||||||
pub(crate) distributions: OnceMap<PackageId, Metadata21>,
|
pub(crate) distributions: OnceMap<PackageId, Metadata23>,
|
||||||
|
|
||||||
/// A map from source URL to precise URL. For example, the source URL
|
/// A map from source URL to precise URL. For example, the source URL
|
||||||
/// `git+https://github.com/pallets/flask.git` could be redirected to
|
/// `git+https://github.com/pallets/flask.git` could be redirected to
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use distribution_types::{
|
||||||
use pep440_rs::{Version, VersionSpecifiers, MIN_VERSION};
|
use pep440_rs::{Version, VersionSpecifiers, MIN_VERSION};
|
||||||
use pep508_rs::{MarkerEnvironment, Requirement};
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
use platform_tags::{IncompatibleTag, Tags};
|
use platform_tags::{IncompatibleTag, Tags};
|
||||||
use pypi_types::{Metadata21, Yanked};
|
use pypi_types::{Metadata23, Yanked};
|
||||||
pub(crate) use urls::Urls;
|
pub(crate) use urls::Urls;
|
||||||
use uv_client::{FlatIndex, RegistryClient};
|
use uv_client::{FlatIndex, RegistryClient};
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
|
|
@ -1133,7 +1133,7 @@ enum Response {
|
||||||
/// The returned metadata for a distribution.
|
/// The returned metadata for a distribution.
|
||||||
Dist {
|
Dist {
|
||||||
dist: Dist,
|
dist: Dist,
|
||||||
metadata: Metadata21,
|
metadata: Metadata23,
|
||||||
precise: Option<Url>,
|
precise: Option<Url>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use url::Url;
|
||||||
|
|
||||||
use distribution_types::{Dist, IndexLocations};
|
use distribution_types::{Dist, IndexLocations};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::Metadata23;
|
||||||
use uv_client::{FlatIndex, RegistryClient};
|
use uv_client::{FlatIndex, RegistryClient};
|
||||||
use uv_distribution::DistributionDatabase;
|
use uv_distribution::DistributionDatabase;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::python_requirement::PythonRequirement;
|
||||||
use crate::version_map::VersionMap;
|
use crate::version_map::VersionMap;
|
||||||
|
|
||||||
pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
|
pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
|
||||||
pub type WheelMetadataResult = Result<(Metadata21, Option<Url>), uv_distribution::Error>;
|
pub type WheelMetadataResult = Result<(Metadata23, Option<Url>), uv_distribution::Error>;
|
||||||
|
|
||||||
/// The response when requesting versions for a package
|
/// The response when requesting versions for a package
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -4952,3 +4952,29 @@ dev = ["setuptools"]
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a source distribution that leverages Metadata 2.2.
|
||||||
|
#[test]
|
||||||
|
fn metadata_2_2() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("pyo3-mixed @ https://files.pythonhosted.org/packages/2b/b8/e04b783d3569d5b61b1dcdfda683ac2e3617340539aecd0f099fbade0b4a/pyo3_mixed-2.1.5.tar.gz")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.compile()
|
||||||
|
.arg("requirements.in"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in
|
||||||
|
boltons==23.1.1
|
||||||
|
# via pyo3-mixed
|
||||||
|
pyo3-mixed @ https://files.pythonhosted.org/packages/2b/b8/e04b783d3569d5b61b1dcdfda683ac2e3617340539aecd0f099fbade0b4a/pyo3_mixed-2.1.5.tar.gz
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue