mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 20:31:12 +00:00
Make Directory its own distribution kind (#3519)
## Summary I think this is overall good change because it explicitly encodes (in the type system) something that was previously implicit. I'm not a huge fan of the names here, open to input. It covers some of https://github.com/astral-sh/uv/issues/3506 but I don't think it _closes_ it.
This commit is contained in:
parent
6bbfe555be
commit
42c3bfa351
16 changed files with 434 additions and 163 deletions
|
|
@ -6,7 +6,7 @@ use url::Url;
|
||||||
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
use crate::{GitSourceDist, Name, PathSourceDist, SourceDist};
|
use crate::{DirectorySourceDist, GitSourceDist, Name, PathSourceDist, SourceDist};
|
||||||
|
|
||||||
/// A reference to a source that can be built into a built distribution.
|
/// A reference to a source that can be built into a built distribution.
|
||||||
///
|
///
|
||||||
|
|
@ -62,6 +62,7 @@ pub enum SourceUrl<'a> {
|
||||||
Direct(DirectSourceUrl<'a>),
|
Direct(DirectSourceUrl<'a>),
|
||||||
Git(GitSourceUrl<'a>),
|
Git(GitSourceUrl<'a>),
|
||||||
Path(PathSourceUrl<'a>),
|
Path(PathSourceUrl<'a>),
|
||||||
|
Directory(DirectorySourceUrl<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SourceUrl<'a> {
|
impl<'a> SourceUrl<'a> {
|
||||||
|
|
@ -71,6 +72,7 @@ impl<'a> SourceUrl<'a> {
|
||||||
Self::Direct(dist) => dist.url,
|
Self::Direct(dist) => dist.url,
|
||||||
Self::Git(dist) => dist.url,
|
Self::Git(dist) => dist.url,
|
||||||
Self::Path(dist) => dist.url,
|
Self::Path(dist) => dist.url,
|
||||||
|
Self::Directory(dist) => dist.url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +83,7 @@ impl std::fmt::Display for SourceUrl<'_> {
|
||||||
Self::Direct(url) => write!(f, "{url}"),
|
Self::Direct(url) => write!(f, "{url}"),
|
||||||
Self::Git(url) => write!(f, "{url}"),
|
Self::Git(url) => write!(f, "{url}"),
|
||||||
Self::Path(url) => write!(f, "{url}"),
|
Self::Path(url) => write!(f, "{url}"),
|
||||||
|
Self::Directory(url) => write!(f, "{url}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,3 +136,24 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DirectorySourceUrl<'a> {
|
||||||
|
pub url: &'a Url,
|
||||||
|
pub path: Cow<'a, Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DirectorySourceUrl<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{url}", url = self.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
|
||||||
|
fn from(dist: &'a DirectorySourceDist) -> Self {
|
||||||
|
Self {
|
||||||
|
url: &dist.url,
|
||||||
|
path: Cow::Borrowed(&dist.path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,13 @@ impl CachedDist {
|
||||||
editable: false,
|
editable: false,
|
||||||
}),
|
}),
|
||||||
Dist::Source(SourceDist::Path(dist)) => Self::Url(CachedDirectUrlDist {
|
Dist::Source(SourceDist::Path(dist)) => Self::Url(CachedDirectUrlDist {
|
||||||
|
filename,
|
||||||
|
url: dist.url,
|
||||||
|
hashes,
|
||||||
|
path,
|
||||||
|
editable: false,
|
||||||
|
}),
|
||||||
|
Dist::Source(SourceDist::Directory(dist)) => Self::Url(CachedDirectUrlDist {
|
||||||
filename,
|
filename,
|
||||||
url: dist.url,
|
url: dist.url,
|
||||||
hashes,
|
hashes,
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ pub enum SourceDist {
|
||||||
DirectUrl(DirectUrlSourceDist),
|
DirectUrl(DirectUrlSourceDist),
|
||||||
Git(GitSourceDist),
|
Git(GitSourceDist),
|
||||||
Path(PathSourceDist),
|
Path(PathSourceDist),
|
||||||
|
Directory(DirectorySourceDist),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A built distribution (wheel) that exists in a registry, like `PyPI`.
|
/// A built distribution (wheel) that exists in a registry, like `PyPI`.
|
||||||
|
|
@ -203,12 +204,20 @@ pub struct GitSourceDist {
|
||||||
pub url: VerbatimUrl,
|
pub url: VerbatimUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A source distribution that exists in a local directory.
|
/// A source distribution that exists in a local archive (e.g., a `.tar.gz` file).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PathSourceDist {
|
pub struct PathSourceDist {
|
||||||
pub name: PackageName,
|
pub name: PackageName,
|
||||||
pub url: VerbatimUrl,
|
pub url: VerbatimUrl,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A source distribution that exists in a local directory.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DirectorySourceDist {
|
||||||
|
pub name: PackageName,
|
||||||
|
pub url: VerbatimUrl,
|
||||||
|
pub path: PathBuf,
|
||||||
pub editable: bool,
|
pub editable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +290,15 @@ impl Dist {
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if path
|
// Determine whether the path represents an archive or a directory.
|
||||||
|
if path.is_dir() {
|
||||||
|
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
editable,
|
||||||
|
})))
|
||||||
|
} else if path
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
||||||
{
|
{
|
||||||
|
|
@ -305,11 +322,14 @@ impl Dist {
|
||||||
path,
|
path,
|
||||||
})))
|
})))
|
||||||
} else {
|
} else {
|
||||||
|
if editable {
|
||||||
|
return Err(Error::EditableFile(url));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
path,
|
path,
|
||||||
editable,
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +402,7 @@ impl Dist {
|
||||||
/// Create a [`Dist`] for a local editable distribution.
|
/// Create a [`Dist`] for a local editable distribution.
|
||||||
pub fn from_editable(name: PackageName, editable: LocalEditable) -> Result<Self, Error> {
|
pub fn from_editable(name: PackageName, editable: LocalEditable) -> Result<Self, Error> {
|
||||||
let LocalEditable { url, path, .. } = editable;
|
let LocalEditable { url, path, .. } = editable;
|
||||||
Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
path,
|
path,
|
||||||
|
|
@ -454,7 +474,7 @@ impl SourceDist {
|
||||||
pub fn index(&self) -> Option<&IndexUrl> {
|
pub fn index(&self) -> Option<&IndexUrl> {
|
||||||
match self {
|
match self {
|
||||||
Self::Registry(registry) => Some(®istry.index),
|
Self::Registry(registry) => Some(®istry.index),
|
||||||
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) => None,
|
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,14 +482,14 @@ impl SourceDist {
|
||||||
pub fn file(&self) -> Option<&File> {
|
pub fn file(&self) -> Option<&File> {
|
||||||
match self {
|
match self {
|
||||||
Self::Registry(registry) => Some(®istry.file),
|
Self::Registry(registry) => Some(®istry.file),
|
||||||
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) => None,
|
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> Option<&Version> {
|
pub fn version(&self) -> Option<&Version> {
|
||||||
match self {
|
match self {
|
||||||
Self::Registry(source_dist) => Some(&source_dist.filename.version),
|
Self::Registry(source_dist) => Some(&source_dist.filename.version),
|
||||||
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) => None,
|
Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -487,7 +507,7 @@ impl SourceDist {
|
||||||
/// Return true if the distribution is editable.
|
/// Return true if the distribution is editable.
|
||||||
pub fn is_editable(&self) -> bool {
|
pub fn is_editable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Path(PathSourceDist { editable, .. }) => *editable,
|
Self::Directory(DirectorySourceDist { editable, .. }) => *editable,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -496,6 +516,7 @@ impl SourceDist {
|
||||||
pub fn as_path(&self) -> Option<&Path> {
|
pub fn as_path(&self) -> Option<&Path> {
|
||||||
match self {
|
match self {
|
||||||
Self::Path(dist) => Some(&dist.path),
|
Self::Path(dist) => Some(&dist.path),
|
||||||
|
Self::Directory(dist) => Some(&dist.path),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -543,6 +564,12 @@ impl Name for PathSourceDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Name for DirectorySourceDist {
|
||||||
|
fn name(&self) -> &PackageName {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Name for SourceDist {
|
impl Name for SourceDist {
|
||||||
fn name(&self) -> &PackageName {
|
fn name(&self) -> &PackageName {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -550,6 +577,7 @@ impl Name for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.name(),
|
Self::DirectUrl(dist) => dist.name(),
|
||||||
Self::Git(dist) => dist.name(),
|
Self::Git(dist) => dist.name(),
|
||||||
Self::Path(dist) => dist.name(),
|
Self::Path(dist) => dist.name(),
|
||||||
|
Self::Directory(dist) => dist.name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -615,6 +643,12 @@ impl DistributionMetadata for PathSourceDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DistributionMetadata for DirectorySourceDist {
|
||||||
|
fn version_or_url(&self) -> VersionOrUrlRef {
|
||||||
|
VersionOrUrlRef::Url(&self.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DistributionMetadata for SourceDist {
|
impl DistributionMetadata for SourceDist {
|
||||||
fn version_or_url(&self) -> VersionOrUrlRef {
|
fn version_or_url(&self) -> VersionOrUrlRef {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -622,6 +656,7 @@ impl DistributionMetadata for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.version_or_url(),
|
Self::DirectUrl(dist) => dist.version_or_url(),
|
||||||
Self::Git(dist) => dist.version_or_url(),
|
Self::Git(dist) => dist.version_or_url(),
|
||||||
Self::Path(dist) => dist.version_or_url(),
|
Self::Path(dist) => dist.version_or_url(),
|
||||||
|
Self::Directory(dist) => dist.version_or_url(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -760,6 +795,16 @@ impl RemoteSource for PathSourceDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RemoteSource for DirectorySourceDist {
|
||||||
|
fn filename(&self) -> Result<Cow<'_, str>, Error> {
|
||||||
|
self.url.filename()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Option<u64> {
|
||||||
|
self.url.size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RemoteSource for SourceDist {
|
impl RemoteSource for SourceDist {
|
||||||
fn filename(&self) -> Result<Cow<'_, str>, Error> {
|
fn filename(&self) -> Result<Cow<'_, str>, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -767,6 +812,7 @@ impl RemoteSource for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.filename(),
|
Self::DirectUrl(dist) => dist.filename(),
|
||||||
Self::Git(dist) => dist.filename(),
|
Self::Git(dist) => dist.filename(),
|
||||||
Self::Path(dist) => dist.filename(),
|
Self::Path(dist) => dist.filename(),
|
||||||
|
Self::Directory(dist) => dist.filename(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -776,6 +822,7 @@ impl RemoteSource for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.size(),
|
Self::DirectUrl(dist) => dist.size(),
|
||||||
Self::Git(dist) => dist.size(),
|
Self::Git(dist) => dist.size(),
|
||||||
Self::Path(dist) => dist.size(),
|
Self::Path(dist) => dist.size(),
|
||||||
|
Self::Directory(dist) => dist.size(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -934,6 +981,16 @@ impl Identifier for PathSourceDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Identifier for DirectorySourceDist {
|
||||||
|
fn distribution_id(&self) -> DistributionId {
|
||||||
|
self.url.distribution_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_id(&self) -> ResourceId {
|
||||||
|
self.url.resource_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Identifier for GitSourceDist {
|
impl Identifier for GitSourceDist {
|
||||||
fn distribution_id(&self) -> DistributionId {
|
fn distribution_id(&self) -> DistributionId {
|
||||||
self.url.distribution_id()
|
self.url.distribution_id()
|
||||||
|
|
@ -951,6 +1008,7 @@ impl Identifier for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.distribution_id(),
|
Self::DirectUrl(dist) => dist.distribution_id(),
|
||||||
Self::Git(dist) => dist.distribution_id(),
|
Self::Git(dist) => dist.distribution_id(),
|
||||||
Self::Path(dist) => dist.distribution_id(),
|
Self::Path(dist) => dist.distribution_id(),
|
||||||
|
Self::Directory(dist) => dist.distribution_id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -960,6 +1018,7 @@ impl Identifier for SourceDist {
|
||||||
Self::DirectUrl(dist) => dist.resource_id(),
|
Self::DirectUrl(dist) => dist.resource_id(),
|
||||||
Self::Git(dist) => dist.resource_id(),
|
Self::Git(dist) => dist.resource_id(),
|
||||||
Self::Path(dist) => dist.resource_id(),
|
Self::Path(dist) => dist.resource_id(),
|
||||||
|
Self::Directory(dist) => dist.resource_id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1038,12 +1097,23 @@ impl Identifier for PathSourceUrl<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Identifier for DirectorySourceUrl<'_> {
|
||||||
|
fn distribution_id(&self) -> DistributionId {
|
||||||
|
self.url.distribution_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_id(&self) -> ResourceId {
|
||||||
|
self.url.resource_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Identifier for SourceUrl<'_> {
|
impl Identifier for SourceUrl<'_> {
|
||||||
fn distribution_id(&self) -> DistributionId {
|
fn distribution_id(&self) -> DistributionId {
|
||||||
match self {
|
match self {
|
||||||
Self::Direct(url) => url.distribution_id(),
|
Self::Direct(url) => url.distribution_id(),
|
||||||
Self::Git(url) => url.distribution_id(),
|
Self::Git(url) => url.distribution_id(),
|
||||||
Self::Path(url) => url.distribution_id(),
|
Self::Path(url) => url.distribution_id(),
|
||||||
|
Self::Directory(url) => url.distribution_id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1052,6 +1122,7 @@ impl Identifier for SourceUrl<'_> {
|
||||||
Self::Direct(url) => url.resource_id(),
|
Self::Direct(url) => url.resource_id(),
|
||||||
Self::Git(url) => url.resource_id(),
|
Self::Git(url) => url.resource_id(),
|
||||||
Self::Path(url) => url.resource_id(),
|
Self::Path(url) => url.resource_id(),
|
||||||
|
Self::Directory(url) => url.resource_id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,11 @@ impl From<&ResolvedDist> for Requirement {
|
||||||
url: sdist.url.clone(),
|
url: sdist.url.clone(),
|
||||||
editable: None,
|
editable: None,
|
||||||
},
|
},
|
||||||
|
Dist::Source(SourceDist::Directory(sdist)) => RequirementSource::Path {
|
||||||
|
path: sdist.path.clone(),
|
||||||
|
url: sdist.url.clone(),
|
||||||
|
editable: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ResolvedDist::Installed(dist) => RequirementSource::Registry {
|
ResolvedDist::Installed(dist) => RequirementSource::Registry {
|
||||||
specifier: pep440_rs::VersionSpecifiers::from(
|
specifier: pep440_rs::VersionSpecifiers::from(
|
||||||
|
|
|
||||||
|
|
@ -770,41 +770,7 @@ impl ArchiveTimestamp {
|
||||||
if metadata.is_file() {
|
if metadata.is_file() {
|
||||||
Ok(Some(Self::Exact(Timestamp::from_metadata(&metadata))))
|
Ok(Some(Self::Exact(Timestamp::from_metadata(&metadata))))
|
||||||
} else {
|
} else {
|
||||||
// Compute the modification timestamp for the `pyproject.toml`, `setup.py`, and
|
Self::from_source_tree(path)
|
||||||
// `setup.cfg` files, if they exist.
|
|
||||||
let pyproject_toml = path
|
|
||||||
.as_ref()
|
|
||||||
.join("pyproject.toml")
|
|
||||||
.metadata()
|
|
||||||
.ok()
|
|
||||||
.filter(std::fs::Metadata::is_file)
|
|
||||||
.as_ref()
|
|
||||||
.map(Timestamp::from_metadata);
|
|
||||||
|
|
||||||
let setup_py = path
|
|
||||||
.as_ref()
|
|
||||||
.join("setup.py")
|
|
||||||
.metadata()
|
|
||||||
.ok()
|
|
||||||
.filter(std::fs::Metadata::is_file)
|
|
||||||
.as_ref()
|
|
||||||
.map(Timestamp::from_metadata);
|
|
||||||
|
|
||||||
let setup_cfg = path
|
|
||||||
.as_ref()
|
|
||||||
.join("setup.cfg")
|
|
||||||
.metadata()
|
|
||||||
.ok()
|
|
||||||
.filter(std::fs::Metadata::is_file)
|
|
||||||
.as_ref()
|
|
||||||
.map(Timestamp::from_metadata);
|
|
||||||
|
|
||||||
// Take the most recent timestamp of the three files.
|
|
||||||
let Some(timestamp) = max(pyproject_toml, max(setup_py, setup_cfg)) else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(Self::Approximate(timestamp)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -814,6 +780,48 @@ impl ArchiveTimestamp {
|
||||||
Ok(Self::Exact(Timestamp::from_metadata(&metadata)))
|
Ok(Self::Exact(Timestamp::from_metadata(&metadata)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the modification timestamp for a source tree, i.e., a directory.
|
||||||
|
///
|
||||||
|
/// If the source tree doesn't contain an entrypoint (i.e., no `pyproject.toml`, `setup.py`, or
|
||||||
|
/// `setup.cfg`), returns `None`.
|
||||||
|
pub fn from_source_tree(path: impl AsRef<Path>) -> Result<Option<Self>, io::Error> {
|
||||||
|
// Compute the modification timestamp for the `pyproject.toml`, `setup.py`, and
|
||||||
|
// `setup.cfg` files, if they exist.
|
||||||
|
let pyproject_toml = path
|
||||||
|
.as_ref()
|
||||||
|
.join("pyproject.toml")
|
||||||
|
.metadata()
|
||||||
|
.ok()
|
||||||
|
.filter(std::fs::Metadata::is_file)
|
||||||
|
.as_ref()
|
||||||
|
.map(Timestamp::from_metadata);
|
||||||
|
|
||||||
|
let setup_py = path
|
||||||
|
.as_ref()
|
||||||
|
.join("setup.py")
|
||||||
|
.metadata()
|
||||||
|
.ok()
|
||||||
|
.filter(std::fs::Metadata::is_file)
|
||||||
|
.as_ref()
|
||||||
|
.map(Timestamp::from_metadata);
|
||||||
|
|
||||||
|
let setup_cfg = path
|
||||||
|
.as_ref()
|
||||||
|
.join("setup.cfg")
|
||||||
|
.metadata()
|
||||||
|
.ok()
|
||||||
|
.filter(std::fs::Metadata::is_file)
|
||||||
|
.as_ref()
|
||||||
|
.map(Timestamp::from_metadata);
|
||||||
|
|
||||||
|
// Take the most recent timestamp of the three files.
|
||||||
|
let Some(timestamp) = max(pyproject_toml, max(setup_py, setup_cfg)) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(Self::Approximate(timestamp)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the modification timestamp for an archive.
|
/// Return the modification timestamp for an archive.
|
||||||
pub fn timestamp(&self) -> Timestamp {
|
pub fn timestamp(&self) -> Timestamp {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
git_reference, DirectUrlSourceDist, GitSourceDist, Hashed, PathSourceDist,
|
git_reference, DirectUrlSourceDist, DirectorySourceDist, GitSourceDist, Hashed, PathSourceDist,
|
||||||
};
|
};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, CacheShard, WheelCache};
|
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, CacheShard, WheelCache};
|
||||||
|
|
@ -67,9 +67,43 @@ impl<'a> BuiltWheelIndex<'a> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Determine the last-modified time of the source distribution.
|
||||||
|
let modified = ArchiveTimestamp::from_file(&source_dist.path).map_err(Error::CacheRead)?;
|
||||||
|
|
||||||
|
// If the distribution is stale, omit it from the index.
|
||||||
|
if !pointer.is_up_to_date(modified) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce hash-checking by omitting any wheels that don't satisfy the required hashes.
|
||||||
|
let revision = pointer.into_revision();
|
||||||
|
if !revision.satisfies(self.hasher.get(source_dist)) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.find(&cache_shard.shard(revision.id())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the most compatible [`CachedWheel`] for a given source distribution built from a
|
||||||
|
/// local directory (source tree).
|
||||||
|
pub fn directory(
|
||||||
|
&self,
|
||||||
|
source_dist: &DirectorySourceDist,
|
||||||
|
) -> Result<Option<CachedWheel>, Error> {
|
||||||
|
let cache_shard = self.cache.shard(
|
||||||
|
CacheBucket::BuiltWheels,
|
||||||
|
WheelCache::Path(&source_dist.url).root(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read the revision from the cache.
|
||||||
|
let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
// Determine the last-modified time of the source distribution.
|
// Determine the last-modified time of the source distribution.
|
||||||
let Some(modified) =
|
let Some(modified) =
|
||||||
ArchiveTimestamp::from_path(&source_dist.path).map_err(Error::CacheRead)?
|
ArchiveTimestamp::from_source_tree(&source_dist.path).map_err(Error::CacheRead)?
|
||||||
else {
|
else {
|
||||||
return Err(Error::DirWithoutEntrypoint);
|
return Err(Error::DirWithoutEntrypoint);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,9 @@ use zip::ZipArchive;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, Dist, FileLocation, GitSourceUrl, HashPolicy, Hashed, LocalEditable,
|
BuildableSource, DirectorySourceDist, DirectorySourceUrl, Dist, FileLocation, GitSourceUrl,
|
||||||
ParsedArchiveUrl, PathSourceDist, PathSourceUrl, RemoteSource, SourceDist, SourceUrl,
|
HashPolicy, Hashed, LocalEditable, ParsedArchiveUrl, PathSourceUrl, RemoteSource, SourceDist,
|
||||||
|
SourceUrl,
|
||||||
};
|
};
|
||||||
use install_wheel_rs::metadata::read_archive_metadata;
|
use install_wheel_rs::metadata::read_archive_metadata;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
|
|
@ -163,26 +164,25 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
BuildableSource::Dist(SourceDist::Path(dist)) => {
|
BuildableSource::Dist(SourceDist::Directory(dist)) => {
|
||||||
if dist.path.is_dir() {
|
self.source_tree(source, &DirectorySourceUrl::from(dist), tags, hashes)
|
||||||
self.source_tree(source, &PathSourceUrl::from(dist), tags, hashes)
|
|
||||||
.boxed_local()
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
let cache_shard = self
|
|
||||||
.build_context
|
|
||||||
.cache()
|
|
||||||
.shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root());
|
|
||||||
self.archive(
|
|
||||||
source,
|
|
||||||
&PathSourceUrl::from(dist),
|
|
||||||
&cache_shard,
|
|
||||||
tags,
|
|
||||||
hashes,
|
|
||||||
)
|
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
BuildableSource::Dist(SourceDist::Path(dist)) => {
|
||||||
|
let cache_shard = self
|
||||||
|
.build_context
|
||||||
|
.cache()
|
||||||
|
.shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root());
|
||||||
|
self.archive(
|
||||||
|
source,
|
||||||
|
&PathSourceUrl::from(dist),
|
||||||
|
&cache_shard,
|
||||||
|
tags,
|
||||||
|
hashes,
|
||||||
|
)
|
||||||
|
.boxed_local()
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
BuildableSource::Url(SourceUrl::Direct(resource)) => {
|
BuildableSource::Url(SourceUrl::Direct(resource)) => {
|
||||||
let filename = resource
|
let filename = resource
|
||||||
|
|
@ -216,20 +216,19 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
BuildableSource::Url(SourceUrl::Directory(resource)) => {
|
||||||
|
self.source_tree(source, resource, tags, hashes)
|
||||||
|
.boxed_local()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
||||||
if resource.path.is_dir() {
|
let cache_shard = self.build_context.cache().shard(
|
||||||
self.source_tree(source, resource, tags, hashes)
|
CacheBucket::BuiltWheels,
|
||||||
.boxed_local()
|
WheelCache::Path(resource.url).root(),
|
||||||
.await?
|
);
|
||||||
} else {
|
self.archive(source, resource, &cache_shard, tags, hashes)
|
||||||
let cache_shard = self.build_context.cache().shard(
|
.boxed_local()
|
||||||
CacheBucket::BuiltWheels,
|
.await?
|
||||||
WheelCache::Path(resource.url).root(),
|
|
||||||
);
|
|
||||||
self.archive(source, resource, &cache_shard, tags, hashes)
|
|
||||||
.boxed_local()
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -319,20 +318,19 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
BuildableSource::Dist(SourceDist::Directory(dist)) => {
|
||||||
|
self.source_tree_metadata(source, &DirectorySourceUrl::from(dist), hashes)
|
||||||
|
.boxed_local()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
BuildableSource::Dist(SourceDist::Path(dist)) => {
|
BuildableSource::Dist(SourceDist::Path(dist)) => {
|
||||||
if dist.path.is_dir() {
|
let cache_shard = self
|
||||||
self.source_tree_metadata(source, &PathSourceUrl::from(dist), hashes)
|
.build_context
|
||||||
.boxed_local()
|
.cache()
|
||||||
.await?
|
.shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root());
|
||||||
} else {
|
self.archive_metadata(source, &PathSourceUrl::from(dist), &cache_shard, hashes)
|
||||||
let cache_shard = self
|
.boxed_local()
|
||||||
.build_context
|
.await?
|
||||||
.cache()
|
|
||||||
.shard(CacheBucket::BuiltWheels, WheelCache::Path(&dist.url).root());
|
|
||||||
self.archive_metadata(source, &PathSourceUrl::from(dist), &cache_shard, hashes)
|
|
||||||
.boxed_local()
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BuildableSource::Url(SourceUrl::Direct(resource)) => {
|
BuildableSource::Url(SourceUrl::Direct(resource)) => {
|
||||||
let filename = resource
|
let filename = resource
|
||||||
|
|
@ -365,20 +363,20 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
BuildableSource::Url(SourceUrl::Directory(resource)) => {
|
||||||
|
self.source_tree_metadata(source, resource, hashes)
|
||||||
|
.boxed_local()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
BuildableSource::Url(SourceUrl::Path(resource)) => {
|
||||||
if resource.path.is_dir() {
|
let cache_shard = self.build_context.cache().shard(
|
||||||
self.source_tree_metadata(source, resource, hashes)
|
CacheBucket::BuiltWheels,
|
||||||
.boxed_local()
|
WheelCache::Path(resource.url).root(),
|
||||||
.await?
|
);
|
||||||
} else {
|
self.archive_metadata(source, resource, &cache_shard, hashes)
|
||||||
let cache_shard = self.build_context.cache().shard(
|
.boxed_local()
|
||||||
CacheBucket::BuiltWheels,
|
.await?
|
||||||
WheelCache::Path(resource.url).root(),
|
|
||||||
);
|
|
||||||
self.archive_metadata(source, resource, &cache_shard, hashes)
|
|
||||||
.boxed_local()
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -826,7 +824,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
async fn source_tree(
|
async fn source_tree(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
resource: &PathSourceUrl<'_>,
|
resource: &DirectorySourceUrl<'_>,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
hashes: HashPolicy<'_>,
|
hashes: HashPolicy<'_>,
|
||||||
) -> Result<BuiltWheelMetadata, Error> {
|
) -> Result<BuiltWheelMetadata, Error> {
|
||||||
|
|
@ -891,7 +889,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
async fn source_tree_metadata(
|
async fn source_tree_metadata(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
resource: &PathSourceUrl<'_>,
|
resource: &DirectorySourceUrl<'_>,
|
||||||
hashes: HashPolicy<'_>,
|
hashes: HashPolicy<'_>,
|
||||||
) -> Result<ArchiveMetadata, Error> {
|
) -> Result<ArchiveMetadata, Error> {
|
||||||
// Before running the build, check that the hashes match.
|
// Before running the build, check that the hashes match.
|
||||||
|
|
@ -967,12 +965,12 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
async fn source_tree_revision(
|
async fn source_tree_revision(
|
||||||
&self,
|
&self,
|
||||||
source: &BuildableSource<'_>,
|
source: &BuildableSource<'_>,
|
||||||
resource: &PathSourceUrl<'_>,
|
resource: &DirectorySourceUrl<'_>,
|
||||||
cache_shard: &CacheShard,
|
cache_shard: &CacheShard,
|
||||||
) -> Result<Revision, Error> {
|
) -> Result<Revision, Error> {
|
||||||
// Determine the last-modified time of the source distribution.
|
// Determine the last-modified time of the source distribution.
|
||||||
let Some(modified) =
|
let Some(modified) =
|
||||||
ArchiveTimestamp::from_path(&resource.path).map_err(Error::CacheRead)?
|
ArchiveTimestamp::from_source_tree(&resource.path).map_err(Error::CacheRead)?
|
||||||
else {
|
else {
|
||||||
return Err(Error::DirWithoutEntrypoint);
|
return Err(Error::DirWithoutEntrypoint);
|
||||||
};
|
};
|
||||||
|
|
@ -1432,8 +1430,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
.await
|
.await
|
||||||
.map_err(|err| Error::BuildEditable(editable.to_string(), err))?;
|
.map_err(|err| Error::BuildEditable(editable.to_string(), err))?;
|
||||||
let filename = WheelFilename::from_str(&disk_filename)?;
|
let filename = WheelFilename::from_str(&disk_filename)?;
|
||||||
|
|
||||||
// We finally have the name of the package and can construct the dist.
|
// We finally have the name of the package and can construct the dist.
|
||||||
let dist = Dist::Source(SourceDist::Path(PathSourceDist {
|
let dist = Dist::Source(SourceDist::Directory(DirectorySourceDist {
|
||||||
name: filename.name.clone(),
|
name: filename.name.clone(),
|
||||||
url: editable.url().clone(),
|
url: editable.url().clone(),
|
||||||
path: editable.path.clone(),
|
path: editable.path.clone(),
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ use tracing::{debug, warn};
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
CachedDirectUrlDist, CachedDist, DirectUrlBuiltDist, DirectUrlSourceDist, Error, GitSourceDist,
|
CachedDirectUrlDist, CachedDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist,
|
||||||
Hashed, IndexLocations, InstalledDist, InstalledMetadata, InstalledVersion, Name,
|
Error, GitSourceDist, Hashed, IndexLocations, InstalledDist, InstalledMetadata,
|
||||||
PathBuiltDist, PathSourceDist, RemoteSource, Requirement, RequirementSource, Verbatim,
|
InstalledVersion, Name, PathBuiltDist, PathSourceDist, RemoteSource, Requirement,
|
||||||
|
RequirementSource, Verbatim,
|
||||||
};
|
};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
use uv_cache::{ArchiveTimestamp, Cache, CacheBucket, WheelCache};
|
||||||
|
|
@ -328,7 +329,23 @@ impl<'a> Planner<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if we have a wheel or a source distribution.
|
// Check if we have a wheel or a source distribution.
|
||||||
if path
|
if path.is_dir() {
|
||||||
|
let sdist = DirectorySourceDist {
|
||||||
|
name: requirement.name.clone(),
|
||||||
|
url: url.clone(),
|
||||||
|
path,
|
||||||
|
editable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the most-compatible wheel from the cache, since we don't know
|
||||||
|
// the filename in advance.
|
||||||
|
if let Some(wheel) = built_index.directory(&sdist)? {
|
||||||
|
let cached_dist = wheel.into_url_dist(url.clone());
|
||||||
|
debug!("Path source requirement already cached: {cached_dist}");
|
||||||
|
cached.push(CachedDist::Url(cached_dist));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if path
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
||||||
{
|
{
|
||||||
|
|
@ -395,8 +412,8 @@ impl<'a> Planner<'a> {
|
||||||
name: requirement.name.clone(),
|
name: requirement.name.clone(),
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
path,
|
path,
|
||||||
editable: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find the most-compatible wheel from the cache, since we don't know
|
// Find the most-compatible wheel from the cache, since we don't know
|
||||||
// the filename in advance.
|
// the filename in advance.
|
||||||
if let Some(wheel) = built_index.path(&sdist)? {
|
if let Some(wheel) = built_index.path(&sdist)? {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ use futures::TryStreamExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, HashPolicy, PathSourceUrl, Requirement, SourceUrl, VersionId,
|
BuildableSource, DirectorySourceUrl, HashPolicy, Requirement, SourceUrl, VersionId,
|
||||||
};
|
};
|
||||||
use pep508_rs::RequirementOrigin;
|
use pep508_rs::RequirementOrigin;
|
||||||
|
|
||||||
use uv_distribution::{DistributionDatabase, Reporter};
|
use uv_distribution::{DistributionDatabase, Reporter};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||||
|
|
@ -97,7 +96,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||||
let Ok(url) = Url::from_directory_path(source_tree) else {
|
let Ok(url) = Url::from_directory_path(source_tree) else {
|
||||||
return Err(anyhow::anyhow!("Failed to convert path to URL"));
|
return Err(anyhow::anyhow!("Failed to convert path to URL"));
|
||||||
};
|
};
|
||||||
let source = SourceUrl::Path(PathSourceUrl {
|
let source = SourceUrl::Directory(DirectorySourceUrl {
|
||||||
url: &url,
|
url: &url,
|
||||||
path: Cow::Borrowed(source_tree),
|
path: Cow::Borrowed(source_tree),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ use tracing::debug;
|
||||||
|
|
||||||
use distribution_filename::{SourceDistFilename, WheelFilename};
|
use distribution_filename::{SourceDistFilename, WheelFilename};
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuildableSource, DirectSourceUrl, GitSourceUrl, PathSourceUrl, RemoteSource, Requirement,
|
BuildableSource, DirectSourceUrl, DirectorySourceUrl, GitSourceUrl, PathSourceUrl,
|
||||||
SourceUrl, UnresolvedRequirement, UnresolvedRequirementSpecification, VersionId,
|
RemoteSource, Requirement, SourceUrl, UnresolvedRequirement,
|
||||||
|
UnresolvedRequirementSpecification, VersionId,
|
||||||
};
|
};
|
||||||
use pep508_rs::{Scheme, UnnamedRequirement, VersionOrUrl};
|
use pep508_rs::{Scheme, UnnamedRequirement, VersionOrUrl};
|
||||||
use pypi_types::Metadata10;
|
use pypi_types::Metadata10;
|
||||||
|
|
@ -222,12 +223,17 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SourceUrl::Path(PathSourceUrl {
|
SourceUrl::Directory(DirectorySourceUrl {
|
||||||
url: &requirement.url,
|
url: &requirement.url,
|
||||||
path: Cow::Owned(path),
|
path: Cow::Owned(path),
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
SourceUrl::Path(PathSourceUrl {
|
||||||
|
url: &requirement.url,
|
||||||
|
path: Cow::Owned(path),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(Scheme::Http | Scheme::Https) => SourceUrl::Direct(DirectSourceUrl {
|
Some(Scheme::Http | Scheme::Https) => SourceUrl::Direct(DirectSourceUrl {
|
||||||
url: &requirement.url,
|
url: &requirement.url,
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@ use pubgrub::range::Range;
|
||||||
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, ParsedUrlError, SourceDist};
|
||||||
BuiltDist, IndexLocations, InstalledDist, ParsedUrlError, PathBuiltDist, PathSourceDist,
|
|
||||||
SourceDist,
|
|
||||||
};
|
|
||||||
use once_map::OnceMap;
|
use once_map::OnceMap;
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
|
|
@ -75,14 +72,14 @@ pub enum ResolveError {
|
||||||
FetchAndBuild(Box<SourceDist>, #[source] uv_distribution::Error),
|
FetchAndBuild(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||||
|
|
||||||
#[error("Failed to read `{0}`")]
|
#[error("Failed to read `{0}`")]
|
||||||
Read(Box<PathBuiltDist>, #[source] uv_distribution::Error),
|
Read(Box<BuiltDist>, #[source] uv_distribution::Error),
|
||||||
|
|
||||||
// TODO(zanieb): Use `thiserror` in `InstalledDist` so we can avoid chaining `anyhow`
|
// TODO(zanieb): Use `thiserror` in `InstalledDist` so we can avoid chaining `anyhow`
|
||||||
#[error("Failed to read metadata from installed package `{0}`")]
|
#[error("Failed to read metadata from installed package `{0}`")]
|
||||||
ReadInstalled(Box<InstalledDist>, #[source] anyhow::Error),
|
ReadInstalled(Box<InstalledDist>, #[source] anyhow::Error),
|
||||||
|
|
||||||
#[error("Failed to build `{0}`")]
|
#[error("Failed to build `{0}`")]
|
||||||
Build(Box<PathSourceDist>, #[source] uv_distribution::Error),
|
Build(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
NoSolution(#[from] NoSolutionError),
|
NoSolution(#[from] NoSolutionError),
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ use std::collections::VecDeque;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, Dist, DistributionMetadata, FileLocation,
|
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist,
|
||||||
GitSourceDist, IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist,
|
DistributionMetadata, FileLocation, GitSourceDist, IndexUrl, Name, PathBuiltDist,
|
||||||
RegistrySourceDist, Resolution, ResolvedDist, ToUrlError, VersionOrUrlRef,
|
PathSourceDist, RegistryBuiltDist, RegistrySourceDist, Resolution, ResolvedDist, ToUrlError,
|
||||||
|
VersionOrUrlRef,
|
||||||
};
|
};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||||
|
|
@ -360,6 +361,9 @@ impl Source {
|
||||||
distribution_types::SourceDist::Path(ref path_dist) => {
|
distribution_types::SourceDist::Path(ref path_dist) => {
|
||||||
Source::from_path_source_dist(path_dist)
|
Source::from_path_source_dist(path_dist)
|
||||||
}
|
}
|
||||||
|
distribution_types::SourceDist::Directory(ref directory) => {
|
||||||
|
Source::from_directory_source_dist(directory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,6 +403,13 @@ impl Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_directory_source_dist(directory_dist: &DirectorySourceDist) -> Source {
|
||||||
|
Source {
|
||||||
|
kind: SourceKind::Directory,
|
||||||
|
url: directory_dist.url.to_url(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from_index_url(index_url: &IndexUrl) -> Source {
|
fn from_index_url(index_url: &IndexUrl) -> Source {
|
||||||
match *index_url {
|
match *index_url {
|
||||||
IndexUrl::Pypi(ref verbatim_url) => Source {
|
IndexUrl::Pypi(ref verbatim_url) => Source {
|
||||||
|
|
@ -497,6 +508,7 @@ pub(crate) enum SourceKind {
|
||||||
Git(GitSource),
|
Git(GitSource),
|
||||||
Direct,
|
Direct,
|
||||||
Path,
|
Path,
|
||||||
|
Directory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceKind {
|
impl SourceKind {
|
||||||
|
|
@ -506,6 +518,7 @@ impl SourceKind {
|
||||||
SourceKind::Git(_) => "git",
|
SourceKind::Git(_) => "git",
|
||||||
SourceKind::Direct => "direct",
|
SourceKind::Direct => "direct",
|
||||||
SourceKind::Path => "path",
|
SourceKind::Path => "path",
|
||||||
|
SourceKind::Directory => "directory",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -515,13 +528,8 @@ impl SourceKind {
|
||||||
/// _not_ be present.
|
/// _not_ be present.
|
||||||
fn requires_hash(&self) -> bool {
|
fn requires_hash(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
SourceKind::Registry | SourceKind::Direct => true,
|
SourceKind::Registry | SourceKind::Direct | SourceKind::Path => true,
|
||||||
// TODO: A `Path` dependency, if it points to a specific source
|
SourceKind::Git(_) | SourceKind::Directory => false,
|
||||||
// distribution or wheel, should have a hash. But if it points to a
|
|
||||||
// directory, then it should not have a hash.
|
|
||||||
//
|
|
||||||
// See: https://github.com/astral-sh/uv/issues/3506
|
|
||||||
SourceKind::Git(_) | SourceKind::Path => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -620,6 +628,9 @@ impl SourceDist {
|
||||||
distribution_types::SourceDist::Path(ref path_dist) => {
|
distribution_types::SourceDist::Path(ref path_dist) => {
|
||||||
Ok(SourceDist::from_path_dist(path_dist))
|
Ok(SourceDist::from_path_dist(path_dist))
|
||||||
}
|
}
|
||||||
|
distribution_types::SourceDist::Directory(ref directory_dist) => {
|
||||||
|
Ok(SourceDist::from_directory_dist(directory_dist))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,6 +670,13 @@ impl SourceDist {
|
||||||
hash: None,
|
hash: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_directory_dist(directory_dist: &DirectorySourceDist) -> SourceDist {
|
||||||
|
SourceDist {
|
||||||
|
url: directory_dist.url.to_url(),
|
||||||
|
hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
|
/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
|
||||||
|
|
@ -1148,7 +1166,7 @@ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24b
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hash_required_missing() {
|
fn hash_optional_missing() {
|
||||||
let data = r#"
|
let data = r#"
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1224,10 +1224,13 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| match dist.clone() {
|
.map_err(|err| match dist.clone() {
|
||||||
Dist::Built(BuiltDist::Path(built_dist)) => {
|
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
|
||||||
ResolveError::Read(Box::new(built_dist), err)
|
ResolveError::Read(Box::new(built_dist), err)
|
||||||
}
|
}
|
||||||
Dist::Source(SourceDist::Path(source_dist)) => {
|
Dist::Source(source_dist @ SourceDist::Path(_)) => {
|
||||||
|
ResolveError::Build(Box::new(source_dist), err)
|
||||||
|
}
|
||||||
|
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
|
||||||
ResolveError::Build(Box::new(source_dist), err)
|
ResolveError::Build(Box::new(source_dist), err)
|
||||||
}
|
}
|
||||||
Dist::Built(built_dist) => ResolveError::Fetch(Box::new(built_dist), err),
|
Dist::Built(built_dist) => ResolveError::Fetch(Box::new(built_dist), err),
|
||||||
|
|
@ -1311,10 +1314,13 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| match dist.clone() {
|
.map_err(|err| match dist.clone() {
|
||||||
Dist::Built(BuiltDist::Path(built_dist)) => {
|
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
|
||||||
ResolveError::Read(Box::new(built_dist), err)
|
ResolveError::Read(Box::new(built_dist), err)
|
||||||
}
|
}
|
||||||
Dist::Source(SourceDist::Path(source_dist)) => {
|
Dist::Source(source_dist @ SourceDist::Path(_)) => {
|
||||||
|
ResolveError::Build(Box::new(source_dist), err)
|
||||||
|
}
|
||||||
|
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
|
||||||
ResolveError::Build(Box::new(source_dist), err)
|
ResolveError::Build(Box::new(source_dist), err)
|
||||||
}
|
}
|
||||||
Dist::Built(built_dist) => {
|
Dist::Built(built_dist) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
---
|
||||||
|
source: crates/uv-resolver/src/lock.rs
|
||||||
|
expression: result
|
||||||
|
---
|
||||||
|
Ok(
|
||||||
|
Lock {
|
||||||
|
version: 1,
|
||||||
|
distributions: [
|
||||||
|
Distribution {
|
||||||
|
id: DistributionId {
|
||||||
|
name: PackageName(
|
||||||
|
"anyio",
|
||||||
|
),
|
||||||
|
version: "4.3.0",
|
||||||
|
source: Source {
|
||||||
|
kind: Path,
|
||||||
|
url: Url {
|
||||||
|
scheme: "file",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/foo/bar",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marker: None,
|
||||||
|
sourcedist: None,
|
||||||
|
wheels: [
|
||||||
|
Wheel {
|
||||||
|
url: Url {
|
||||||
|
scheme: "file",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/foo/bar/anyio-4.3.0-py3-none-any.whl",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
hash: Some(
|
||||||
|
Hash(
|
||||||
|
HashDigest {
|
||||||
|
algorithm: Sha256,
|
||||||
|
digest: "048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
filename: WheelFilename {
|
||||||
|
name: PackageName(
|
||||||
|
"anyio",
|
||||||
|
),
|
||||||
|
version: "4.3.0",
|
||||||
|
python_tag: [
|
||||||
|
"py3",
|
||||||
|
],
|
||||||
|
abi_tag: [
|
||||||
|
"none",
|
||||||
|
],
|
||||||
|
platform_tag: [
|
||||||
|
"any",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dependencies: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
by_id: {
|
||||||
|
DistributionId {
|
||||||
|
name: PackageName(
|
||||||
|
"anyio",
|
||||||
|
),
|
||||||
|
version: "4.3.0",
|
||||||
|
source: Source {
|
||||||
|
kind: Path,
|
||||||
|
url: Url {
|
||||||
|
scheme: "file",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "/foo/bar",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/uv-resolver/src/lock.rs
|
|
||||||
expression: result
|
|
||||||
---
|
|
||||||
Err(
|
|
||||||
Error {
|
|
||||||
inner: Error {
|
|
||||||
inner: TomlError {
|
|
||||||
message: "since the distribution `anyio 4.3.0 path+file:///foo/bar` comes from a path dependency, a hash was not expected but one was found for wheel",
|
|
||||||
raw: None,
|
|
||||||
keys: [],
|
|
||||||
span: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
@ -2609,7 +2609,7 @@ requires-python = ">=3.8"
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
// Modify the editable package.
|
// Modify the package.
|
||||||
pyproject_toml.write_str(
|
pyproject_toml.write_str(
|
||||||
r#"[project]
|
r#"[project]
|
||||||
name = "example"
|
name = "example"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue