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:
Charlie Marsh 2024-06-12 12:59:21 -07:00 committed by GitHub
parent c4483017ac
commit d8f1de6134
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 524 additions and 167 deletions

View file

@ -10,7 +10,8 @@ use distribution_types::Verbatim;
use pep440_rs::Version;
use pep508_rs::{MarkerEnvironment, MarkerTree};
use pypi_types::{
ParsedArchiveUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, Requirement, RequirementSource,
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, Requirement,
RequirementSource,
};
use uv_configuration::{Constraints, Overrides};
use uv_git::GitResolver;
@ -346,7 +347,6 @@ impl PubGrubRequirement {
})
}
RequirementSource::Path {
editable,
url,
install_path,
lock_path,
@ -359,6 +359,42 @@ impl PubGrubRequirement {
};
let parsed_url = ParsedUrl::Path(ParsedPathUrl::from_source(
install_path.clone(),
lock_path.clone(),
url.to_url(),
));
if !Urls::same_resource(&expected.parsed_url, &parsed_url, git) {
return Err(ResolveError::ConflictingUrlsTransitive(
requirement.name.clone(),
expected.verbatim.verbatim().to_string(),
url.verbatim().to_string(),
));
}
Ok(Self {
package: PubGrubPackage::from_url(
requirement.name.clone(),
extra,
requirement.marker.clone(),
expected.clone(),
),
version: Range::full(),
})
}
RequirementSource::Directory {
editable,
url,
install_path,
lock_path,
} => {
let Some(expected) = urls.get(&requirement.name) else {
return Err(ResolveError::DisallowedUrl(
requirement.name.clone(),
url.to_string(),
));
};
let parsed_url = ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
install_path.clone(),
lock_path.clone(),
*editable,

View file

@ -196,6 +196,7 @@ fn iter_locals(source: &RequirementSource) -> Box<dyn Iterator<Item = Version> +
.into_iter()
.filter(pep440_rs::Version::is_local),
),
RequirementSource::Directory { .. } => Box::new(iter::empty()),
}
}

View file

@ -6,7 +6,8 @@ use cache_key::CanonicalUrl;
use distribution_types::Verbatim;
use pep508_rs::MarkerEnvironment;
use pypi_types::{
ParsedArchiveUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, RequirementSource, VerbatimParsedUrl,
ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl,
RequirementSource, VerbatimParsedUrl,
};
use uv_git::GitResolver;
use uv_normalize::PackageName;
@ -55,11 +56,34 @@ impl Urls {
RequirementSource::Path {
install_path,
lock_path,
editable,
url,
} => {
let url = VerbatimParsedUrl {
parsed_url: ParsedUrl::Path(ParsedPathUrl::from_source(
install_path.clone(),
lock_path.clone(),
url.to_url(),
)),
verbatim: url.clone(),
};
if let Some(previous) = urls.insert(requirement.name.clone(), url.clone()) {
if !Self::same_resource(&previous.parsed_url, &url.parsed_url, git) {
return Err(ResolveError::ConflictingUrlsDirect(
requirement.name.clone(),
previous.verbatim.verbatim().to_string(),
url.verbatim.verbatim().to_string(),
));
}
}
}
RequirementSource::Directory {
install_path,
lock_path,
editable,
url,
} => {
let url = VerbatimParsedUrl {
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
install_path.clone(),
lock_path.clone(),
*editable,
@ -145,6 +169,10 @@ impl Urls {
a.install_path == b.install_path
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false)
}
(ParsedUrl::Directory(a), ParsedUrl::Directory(b)) => {
a.install_path == b.install_path
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false)
}
_ => false,
}
}