Read package metadata from pyproject.toml when statically defined (#2676)

## Summary

Now that we're resolving metadata more aggressively for local sources,
it's worth doing this. We now pull metadata from the `pyproject.toml`
directly if it's statically-defined.

Closes https://github.com/astral-sh/uv/issues/2629.
This commit is contained in:
Charlie Marsh 2024-03-27 10:34:18 -04:00 committed by GitHub
parent 248d6f89ef
commit 365c292525
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 302 additions and 34 deletions

View file

@ -61,8 +61,12 @@ pub enum Error {
NotFound(PathBuf),
#[error("The source distribution is missing a `PKG-INFO` file")]
MissingPkgInfo,
#[error("The source distribution does not support static metadata")]
#[error("The source distribution does not support static metadata in `PKG-INFO`")]
DynamicPkgInfo(#[source] pypi_types::Error),
#[error("The source distribution is missing a `pyproject.toml` file")]
MissingPyprojectToml,
#[error("The source distribution does not support static metadata in `pyproject.toml`")]
DynamicPyprojectToml(#[source] pypi_types::Error),
#[error("Unsupported scheme in URL: {0}")]
UnsupportedScheme(String),

View file

@ -955,10 +955,10 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
) -> Result<Option<Metadata23>, Error> {
debug!("Preparing metadata for: {source}");
// Attempt to read static metadata from the source distribution.
// Attempt to read static metadata from the `PKG-INFO` file.
match read_pkg_info(source_root).await {
Ok(metadata) => {
debug!("Found static metadata for: {source}");
debug!("Found static `PKG-INFO` for: {source}");
// Validate the metadata.
if let Some(name) = source.name() {
@ -973,7 +973,30 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> {
return Ok(Some(metadata));
}
Err(err @ (Error::MissingPkgInfo | Error::DynamicPkgInfo(_))) => {
debug!("No static metadata available for: {source} ({err:?})");
debug!("No static `PKG-INFO` available for: {source} ({err:?})");
}
Err(err) => return Err(err),
}
// Attempt to read static metadata from the `pyproject.toml`.
match read_pyproject_toml(source_root).await {
Ok(metadata) => {
debug!("Found static `pyproject.toml` for: {source}");
// Validate the metadata.
if let Some(name) = source.name() {
if metadata.name != *name {
return Err(Error::NameMismatch {
metadata: metadata.name,
given: name.clone(),
});
}
}
return Ok(Some(metadata));
}
Err(err @ (Error::MissingPyprojectToml | Error::DynamicPyprojectToml(_))) => {
debug!("No static `pyproject.toml` available for: {source} ({err:?})");
}
Err(err) => return Err(err),
}
@ -1105,6 +1128,25 @@ pub(crate) async fn read_pkg_info(source_tree: &Path) -> Result<Metadata23, Erro
Ok(metadata)
}
/// Read the [`Metadata23`] from a source distribution's `pyproject.tom` file, if it defines static
/// metadata consistent with PEP 621.
pub(crate) async fn read_pyproject_toml(source_tree: &Path) -> Result<Metadata23, Error> {
// Read the `pyproject.toml` file.
let content = match fs::read_to_string(source_tree.join("pyproject.toml")).await {
Ok(content) => content,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Err(Error::MissingPyprojectToml);
}
Err(err) => return Err(Error::CacheRead(err)),
};
// Parse the metadata.
let metadata =
Metadata23::parse_pyproject_toml(&content).map_err(Error::DynamicPyprojectToml)?;
Ok(metadata)
}
/// Read an existing HTTP-cached [`Manifest`], if it exists.
pub(crate) fn read_http_manifest(cache_entry: &CacheEntry) -> Result<Option<Manifest>, Error> {
match fs_err::File::open(cache_entry.path()) {