Refactor editables for supporting them in bluejay commands (#3639)

This is split out from workspaces support, which needs editables in the
bluejay commands. It consists mainly of refactorings:

* Move the `editable` module one level up.
* Introduce a `BuiltEditableMetadata` type for `(LocalEditable,
Metadata23, Requirements)`.
* Add editables to `InstalledPackagesProvider` so we can use
`EmptyInstalledPackages` for them.
This commit is contained in:
konsti 2024-05-20 18:22:12 +02:00 committed by GitHub
parent c32fb8647f
commit 95c9621541
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 229 additions and 140 deletions

View file

@ -1,36 +1,34 @@
use std::hash::BuildHasherDefault;
use rustc_hash::FxHashMap;
use distribution_types::{LocalEditable, Requirements};
use pypi_types::Metadata23;
use uv_normalize::PackageName;
/// A built editable for which we know its dependencies and other static metadata.
#[derive(Debug, Clone)]
pub struct BuiltEditableMetadata {
pub built: LocalEditable,
pub metadata: Metadata23,
pub requirements: Requirements,
}
/// A set of editable packages, indexed by package name.
#[derive(Debug, Default, Clone)]
pub(crate) struct Editables(FxHashMap<PackageName, (LocalEditable, Metadata23, Requirements)>);
pub(crate) struct Editables(FxHashMap<PackageName, BuiltEditableMetadata>);
impl Editables {
/// Create a new set of editables from a set of requirements.
pub(crate) fn from_requirements(
requirements: Vec<(LocalEditable, Metadata23, Requirements)>,
) -> Self {
let mut editables =
FxHashMap::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
for (editable_requirement, metadata, requirements) in requirements {
editables.insert(
metadata.name.clone(),
(editable_requirement, metadata, requirements),
);
}
Self(editables)
pub(crate) fn from_requirements(requirements: Vec<BuiltEditableMetadata>) -> Self {
Self(
requirements
.into_iter()
.map(|editable| (editable.metadata.name.clone(), editable))
.collect(),
)
}
/// Get the editable for a package.
pub(crate) fn get(
&self,
name: &PackageName,
) -> Option<&(LocalEditable, Metadata23, Requirements)> {
pub(crate) fn get(&self, name: &PackageName) -> Option<&BuiltEditableMetadata> {
self.0.get(name)
}
@ -40,7 +38,7 @@ impl Editables {
}
/// Iterate over all editables.
pub(crate) fn iter(&self) -> impl Iterator<Item = &(LocalEditable, Metadata23, Requirements)> {
pub(crate) fn iter(&self) -> impl Iterator<Item = &BuiltEditableMetadata> {
self.0.values()
}
}

View file

@ -1,4 +1,5 @@
pub use dependency_mode::DependencyMode;
pub use editables::BuiltEditableMetadata;
pub use error::ResolveError;
pub use exclude_newer::ExcludeNewer;
pub use exclusions::Exclusions;

View file

@ -1,11 +1,12 @@
use distribution_types::{LocalEditable, Requirement, Requirements};
use either::Either;
use distribution_types::Requirement;
use pep508_rs::MarkerEnvironment;
use pypi_types::Metadata23;
use uv_configuration::{Constraints, Overrides};
use uv_normalize::PackageName;
use uv_types::RequestedRequirements;
use crate::editables::BuiltEditableMetadata;
use crate::{preferences::Preference, DependencyMode, Exclusions};
/// A manifest of requirements, constraints, and preferences.
@ -34,7 +35,7 @@ pub struct Manifest {
///
/// The requirements of the editables should be included in resolution as if they were
/// direct requirements in their own right.
pub(crate) editables: Vec<(LocalEditable, Metadata23, Requirements)>,
pub(crate) editables: Vec<BuiltEditableMetadata>,
/// The installed packages to exclude from consideration during resolution.
///
@ -58,7 +59,7 @@ impl Manifest {
overrides: Overrides,
preferences: Vec<Preference>,
project: Option<PackageName>,
editables: Vec<(LocalEditable, Metadata23, Requirements)>,
editables: Vec<BuiltEditableMetadata>,
exclusions: Exclusions,
lookaheads: Vec<RequestedRequirements>,
) -> Self {
@ -112,11 +113,11 @@ impl Manifest {
requirement.evaluate_markers(markers, lookahead.extras())
})
})
.chain(self.editables.iter().flat_map(move |(editable, _metadata, requirements)| {
.chain(self.editables.iter().flat_map(move |editable| {
self.overrides
.apply(&requirements.dependencies)
.apply(&editable.requirements.dependencies)
.filter(move |requirement| {
requirement.evaluate_markers(markers, &editable.extras)
requirement.evaluate_markers(markers, &editable.built.extras)
})
}))
.chain(
@ -174,15 +175,13 @@ impl Manifest {
requirement.evaluate_markers(markers, lookahead.extras())
})
})
.chain(self.editables.iter().flat_map(
move |(editable, _metadata, uv_requirements)| {
self.overrides.apply(&uv_requirements.dependencies).filter(
move |requirement| {
requirement.evaluate_markers(markers, &editable.extras)
},
)
},
))
.chain(self.editables.iter().flat_map(move |editable| {
self.overrides
.apply(&editable.requirements.dependencies)
.filter(move |requirement| {
requirement.evaluate_markers(markers, &editable.built.extras)
})
}))
.chain(
self.overrides
.apply(&self.requirements)

View file

@ -137,8 +137,8 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
return None;
}
let node = if let Some((editable, _, _)) = self.resolution.editables.get(name) {
Node::Editable(editable)
let node = if let Some(editable) = self.resolution.editables.get(name) {
Node::Editable(&editable.built)
} else {
Node::Distribution(dist)
};

View file

@ -93,14 +93,15 @@ impl ResolutionGraph {
}
}
PubGrubPackage::Package(package_name, Some(extra), Some(url)) => {
if let Some((editable, metadata, _)) = editables.get(package_name) {
if metadata.provides_extras.contains(extra) {
if let Some(editable) = editables.get(package_name) {
if editable.metadata.provides_extras.contains(extra) {
extras
.entry(package_name.clone())
.or_insert_with(Vec::new)
.push(extra.clone());
} else {
let dist = Dist::from_editable(package_name.clone(), editable.clone())?;
let dist =
Dist::from_editable(package_name.clone(), editable.built.clone())?;
diagnostics.push(Diagnostic::MissingExtra {
dist: dist.into(),
@ -219,15 +220,16 @@ impl ResolutionGraph {
}
PubGrubPackage::Package(package_name, None, Some(url)) => {
// Create the distribution.
if let Some((editable, metadata, _)) = editables.get(package_name) {
let dist = Dist::from_editable(package_name.clone(), editable.clone())?;
if let Some(editable) = editables.get(package_name) {
let dist =
Dist::from_editable(package_name.clone(), editable.built.clone())?;
// Add the distribution to the graph.
let index = petgraph.add_node(AnnotatedDist {
dist: dist.into(),
extras: editable.extras.clone(),
extras: editable.built.extras.clone(),
hashes: vec![],
metadata: metadata.clone(),
metadata: editable.metadata.clone(),
});
inverse.insert(package_name, index);
} else {
@ -487,7 +489,7 @@ impl ResolutionGraph {
manifest
.editables
.iter()
.flat_map(|(_, _, uv_requirements)| &uv_requirements.dependencies),
.flat_map(|editable| &editable.requirements.dependencies),
);
for direct_req in manifest.apply(direct_reqs) {
let Some(ref marker_tree) = direct_req.marker else {

View file

@ -728,8 +728,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
);
// If the dist is an editable, return the version from the editable metadata.
if let Some((_local, metadata, _)) = self.editables.get(package_name) {
let version = &metadata.version;
if let Some(editable) = self.editables.get(package_name) {
let version = &editable.metadata.version;
// The version is incompatible with the requirement.
if !range.contains(version) {
@ -737,7 +737,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
// The version is incompatible due to its Python requirement.
if let Some(requires_python) = metadata.requires_python.as_ref() {
if let Some(requires_python) = editable.metadata.requires_python.as_ref() {
let target = self.python_requirement.target();
if !requires_python.contains(target) {
return Ok(Some(ResolverVersion::Unavailable(
@ -947,10 +947,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
// Add a dependency on each editable.
for (editable, metadata, _) in self.editables.iter() {
let package =
PubGrubPackage::from_package(metadata.name.clone(), None, &self.urls);
let version = Range::singleton(metadata.version.clone());
for editable in self.editables.iter() {
let package = PubGrubPackage::from_package(
editable.metadata.name.clone(),
None,
&self.urls,
);
let version = Range::singleton(editable.metadata.version.clone());
// Update the package priorities.
priorities.insert(&package, &version);
@ -959,19 +962,24 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dependencies.push(package, version);
// Add a dependency on each extra.
for extra in &editable.extras {
for extra in &editable.built.extras {
dependencies.push(
PubGrubPackage::from_package(
metadata.name.clone(),
editable.metadata.name.clone(),
Some(extra.clone()),
&self.urls,
),
Range::singleton(metadata.version.clone()),
Range::singleton(editable.metadata.version.clone()),
);
}
// Add any constraints.
for constraint in self.constraints.get(&metadata.name).into_iter().flatten() {
for constraint in self
.constraints
.get(&editable.metadata.name)
.into_iter()
.flatten()
{
if constraint.evaluate_markers(self.markers.as_ref(), &[]) {
let PubGrubRequirement { package, version } =
PubGrubRequirement::from_constraint(
@ -1013,8 +1021,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
// Determine if the distribution is editable.
if let Some((_local, metadata, _)) = self.editables.get(package_name) {
let requirements: Vec<_> = metadata
if let Some(editable) = self.editables.get(package_name) {
let requirements: Vec<_> = editable
.metadata
.requires_dist
.iter()
.cloned()

View file

@ -25,16 +25,18 @@ impl Urls {
let mut urls: FxHashMap<PackageName, VerbatimParsedUrl> = FxHashMap::default();
// Add the editables themselves to the list of required URLs.
for (editable, metadata, _) in &manifest.editables {
for editable in &manifest.editables {
let editable_url = VerbatimParsedUrl {
parsed_url: ParsedUrl::Path(ParsedPathUrl {
url: editable.url.to_url(),
path: editable.path.clone(),
url: editable.built.url.to_url(),
path: editable.built.path.clone(),
editable: true,
}),
verbatim: editable.url.clone(),
verbatim: editable.built.url.clone(),
};
if let Some(previous) = urls.insert(metadata.name.clone(), editable_url.clone()) {
if let Some(previous) =
urls.insert(editable.metadata.name.clone(), editable_url.clone())
{
if !is_equal(&previous.verbatim, &editable_url.verbatim) {
if is_same_reference(&previous.verbatim, &editable_url.verbatim) {
debug!(
@ -43,7 +45,7 @@ impl Urls {
);
} else {
return Err(ResolveError::ConflictingUrlsDirect(
metadata.name.clone(),
editable.metadata.name.clone(),
previous.verbatim.verbatim().to_string(),
editable_url.verbatim.verbatim().to_string(),
));