mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Use separate path types for directories and files (#4285)
## Summary This is what I consider to be the "real" fix for #8072. We now treat directory and path URLs as separate `ParsedUrl` types and `RequirementSource` types. This removes a lot of `.is_dir()` forking within the `ParsedUrl::Path` arms and makes some states impossible (e.g., you can't have a `.whl` path that is editable). It _also_ fixes the `direct_url.json` for direct URLs that refer to files. Previously, we wrote out to these as if they were installed as directories, which is just wrong.
This commit is contained in:
parent
c4483017ac
commit
d8f1de6134
28 changed files with 524 additions and 167 deletions
|
@ -272,7 +272,8 @@ impl<'a> Planner<'a> {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
RequirementSource::Path {
|
||||
|
||||
RequirementSource::Directory {
|
||||
url,
|
||||
editable,
|
||||
install_path,
|
||||
|
@ -287,25 +288,40 @@ impl<'a> Planner<'a> {
|
|||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// Check if we have a wheel or a source distribution.
|
||||
if path.is_dir() {
|
||||
let sdist = DirectorySourceDist {
|
||||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
install_path: path,
|
||||
lock_path: lock_path.clone(),
|
||||
editable: *editable,
|
||||
};
|
||||
let sdist = DirectorySourceDist {
|
||||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
install_path: path,
|
||||
lock_path: lock_path.clone(),
|
||||
editable: *editable,
|
||||
};
|
||||
|
||||
// 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;
|
||||
// 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!("Directory source requirement already cached: {cached_dist}");
|
||||
cached.push(CachedDist::Url(cached_dist));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
RequirementSource::Path {
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
// Store the canonicalized path, which also serves to validate that it exists.
|
||||
let path = match install_path.canonicalize() {
|
||||
Ok(path) => path,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Err(Error::NotFound(url.to_url()).into());
|
||||
}
|
||||
} else if path
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// Check if we have a wheel or a source distribution.
|
||||
if path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
||||
{
|
||||
|
|
|
@ -149,6 +149,52 @@ impl RequirementSatisfaction {
|
|||
Ok(Self::Satisfied)
|
||||
}
|
||||
RequirementSource::Path {
|
||||
install_path: requested_path,
|
||||
lock_path: _,
|
||||
url: _,
|
||||
} => {
|
||||
let InstalledDist::Url(InstalledDirectUrlDist { direct_url, .. }) = &distribution
|
||||
else {
|
||||
return Ok(Self::Mismatch);
|
||||
};
|
||||
let DirectUrl::ArchiveUrl {
|
||||
url: installed_url,
|
||||
archive_info: _,
|
||||
subdirectory: None,
|
||||
} = direct_url.as_ref()
|
||||
else {
|
||||
return Ok(Self::Mismatch);
|
||||
};
|
||||
|
||||
let Some(installed_path) = Url::parse(installed_url)
|
||||
.ok()
|
||||
.and_then(|url| url.to_file_path().ok())
|
||||
else {
|
||||
return Ok(Self::Mismatch);
|
||||
};
|
||||
|
||||
if !(*requested_path == installed_path
|
||||
|| is_same_file(requested_path, &installed_path).unwrap_or(false))
|
||||
{
|
||||
trace!(
|
||||
"Path mismatch: {:?} vs. {:?}",
|
||||
requested_path,
|
||||
installed_path
|
||||
);
|
||||
return Ok(Self::Satisfied);
|
||||
}
|
||||
|
||||
if !ArchiveTimestamp::up_to_date_with(
|
||||
requested_path,
|
||||
ArchiveTarget::Install(distribution),
|
||||
)? {
|
||||
trace!("Installed package is out of date");
|
||||
return Ok(Self::OutOfDate);
|
||||
}
|
||||
|
||||
Ok(Self::Satisfied)
|
||||
}
|
||||
RequirementSource::Directory {
|
||||
install_path: requested_path,
|
||||
lock_path: _,
|
||||
editable: requested_editable,
|
||||
|
@ -205,13 +251,11 @@ impl RequirementSatisfaction {
|
|||
}
|
||||
|
||||
// Does the package have dynamic metadata?
|
||||
// TODO(charlie): Separate `RequirementSource` into `Path` and `Directory`.
|
||||
if requested_path.is_dir() && is_dynamic(requested_path) {
|
||||
if is_dynamic(requested_path) {
|
||||
trace!("Dependency is dynamic");
|
||||
return Ok(Self::Dynamic);
|
||||
}
|
||||
|
||||
// Otherwise, assume the requirement is up-to-date.
|
||||
Ok(Self::Satisfied)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue