mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 21:02:37 +00:00
Add hashes and versions to all distributions (#3589)
## Summary In `ResolutionGraph::from_state`, we have mechanisms to grab the hashes and metadata for all distributions -- but we then throw that information away. This PR preserves it on a new `AnnotatedDist` (yikes, open to suggestions) that wraps `ResolvedDist` and includes (1) the hashes (computed or from the registry) and (2) the `Metadata23`, which lets us extract the version. Closes https://github.com/astral-sh/uv/issues/3356. Closes https://github.com/astral-sh/uv/issues/3357.
This commit is contained in:
parent
7363f31ceb
commit
4a42730cae
3 changed files with 201 additions and 132 deletions
|
|
@ -4,21 +4,23 @@
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist,
|
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist, FileLocation,
|
||||||
DistributionMetadata, FileLocation, GitSourceDist, IndexUrl, Name, PathBuiltDist,
|
GitSourceDist, IndexUrl, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistrySourceDist,
|
||||||
PathSourceDist, RegistryBuiltDist, RegistrySourceDist, Resolution, ResolvedDist, ToUrlError,
|
Resolution, ResolvedDist, ToUrlError,
|
||||||
VersionOrUrlRef,
|
|
||||||
};
|
};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||||
use pypi_types::HashDigest;
|
use pypi_types::HashDigest;
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use url::Url;
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
|
use crate::resolution::AnnotatedDist;
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(into = "LockWire", try_from = "LockWire")]
|
#[serde(into = "LockWire", try_from = "LockWire")]
|
||||||
pub struct Lock {
|
pub struct Lock {
|
||||||
|
|
@ -71,7 +73,7 @@ impl Lock {
|
||||||
let dep_dist = self.find_by_id(&dep.id);
|
let dep_dist = self.find_by_id(&dep.id);
|
||||||
queue.push_back(dep_dist);
|
queue.push_back(dep_dist);
|
||||||
}
|
}
|
||||||
let name = PackageName::new(dist.id.name.to_string()).unwrap();
|
let name = dist.id.name.clone();
|
||||||
let resolved_dist = ResolvedDist::Installable(dist.to_dist(marker_env, tags));
|
let resolved_dist = ResolvedDist::Installable(dist.to_dist(marker_env, tags));
|
||||||
map.insert(name, resolved_dist);
|
map.insert(name, resolved_dist);
|
||||||
}
|
}
|
||||||
|
|
@ -202,15 +204,15 @@ pub(crate) struct Distribution {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Distribution {
|
impl Distribution {
|
||||||
pub(crate) fn from_resolved_dist(
|
pub(crate) fn from_annotated_dist(
|
||||||
resolved_dist: &ResolvedDist,
|
annotated_dist: &AnnotatedDist,
|
||||||
) -> Result<Distribution, LockError> {
|
) -> Result<Distribution, LockError> {
|
||||||
let id = DistributionId::from_resolved_dist(resolved_dist);
|
let id = DistributionId::from_annotated_dist(annotated_dist);
|
||||||
let mut sdist = None;
|
let mut sdist = None;
|
||||||
let mut wheels = vec![];
|
let mut wheels = vec![];
|
||||||
if let Some(dist) = Wheel::from_resolved_dist(resolved_dist)? {
|
if let Some(dist) = Wheel::from_annotated_dist(annotated_dist)? {
|
||||||
wheels.push(dist);
|
wheels.push(dist);
|
||||||
} else if let Some(dist) = SourceDist::from_resolved_dist(resolved_dist)? {
|
} else if let Some(dist) = SourceDist::from_annotated_dist(annotated_dist)? {
|
||||||
sdist = Some(dist);
|
sdist = Some(dist);
|
||||||
}
|
}
|
||||||
Ok(Distribution {
|
Ok(Distribution {
|
||||||
|
|
@ -224,9 +226,9 @@ impl Distribution {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_dependency(&mut self, resolved_dist: &ResolvedDist) {
|
pub(crate) fn add_dependency(&mut self, annotated_dist: &AnnotatedDist) {
|
||||||
self.dependencies
|
self.dependencies
|
||||||
.push(Dependency::from_resolved_dist(resolved_dist));
|
.push(Dependency::from_annotated_dist(annotated_dist));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dist(&self, _marker_env: &MarkerEnvironment, tags: &Tags) -> Dist {
|
fn to_dist(&self, _marker_env: &MarkerEnvironment, tags: &Tags) -> Dist {
|
||||||
|
|
@ -294,16 +296,10 @@ pub(crate) struct DistributionId {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DistributionId {
|
impl DistributionId {
|
||||||
fn from_resolved_dist(resolved_dist: &ResolvedDist) -> DistributionId {
|
fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> DistributionId {
|
||||||
let name = resolved_dist.name().clone();
|
let name = annotated_dist.metadata.name.clone();
|
||||||
let version = match resolved_dist.version_or_url() {
|
let version = annotated_dist.metadata.version.clone();
|
||||||
VersionOrUrlRef::Version(v) => v.clone(),
|
let source = Source::from_resolved_dist(&annotated_dist.dist);
|
||||||
// TODO: We need a way to thread the version number for these
|
|
||||||
// cases down into this routine. The version number isn't yet in a
|
|
||||||
// `ResolutionGraph`, so this will require a bit of refactoring.
|
|
||||||
VersionOrUrlRef::Url(_) => todo!(),
|
|
||||||
};
|
|
||||||
let source = Source::from_resolved_dist(resolved_dist);
|
|
||||||
DistributionId {
|
DistributionId {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
|
@ -596,85 +592,89 @@ pub(crate) struct SourceDist {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceDist {
|
impl SourceDist {
|
||||||
fn from_resolved_dist(resolved_dist: &ResolvedDist) -> Result<Option<SourceDist>, LockError> {
|
fn from_annotated_dist(
|
||||||
match *resolved_dist {
|
annotated_dist: &AnnotatedDist,
|
||||||
|
) -> Result<Option<SourceDist>, LockError> {
|
||||||
|
match annotated_dist.dist {
|
||||||
// TODO: Do we want to try to lock already-installed distributions?
|
// TODO: Do we want to try to lock already-installed distributions?
|
||||||
// Or should we return an error?
|
// Or should we return an error?
|
||||||
ResolvedDist::Installed(_) => todo!(),
|
ResolvedDist::Installed(_) => todo!(),
|
||||||
ResolvedDist::Installable(ref dist) => SourceDist::from_dist(dist),
|
ResolvedDist::Installable(ref dist) => {
|
||||||
|
SourceDist::from_dist(dist, &annotated_dist.hashes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_dist(dist: &Dist) -> Result<Option<SourceDist>, LockError> {
|
fn from_dist(dist: &Dist, hashes: &[HashDigest]) -> Result<Option<SourceDist>, LockError> {
|
||||||
match *dist {
|
match *dist {
|
||||||
Dist::Built(_) => Ok(None),
|
Dist::Built(_) => Ok(None),
|
||||||
Dist::Source(ref source_dist) => SourceDist::from_source_dist(source_dist).map(Some),
|
Dist::Source(ref source_dist) => {
|
||||||
|
SourceDist::from_source_dist(source_dist, hashes).map(Some)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_source_dist(
|
fn from_source_dist(
|
||||||
source_dist: &distribution_types::SourceDist,
|
source_dist: &distribution_types::SourceDist,
|
||||||
|
hashes: &[HashDigest],
|
||||||
) -> Result<SourceDist, LockError> {
|
) -> Result<SourceDist, LockError> {
|
||||||
match *source_dist {
|
match *source_dist {
|
||||||
distribution_types::SourceDist::Registry(ref reg_dist) => {
|
distribution_types::SourceDist::Registry(ref reg_dist) => {
|
||||||
SourceDist::from_registry_dist(reg_dist)
|
SourceDist::from_registry_dist(reg_dist)
|
||||||
}
|
}
|
||||||
distribution_types::SourceDist::DirectUrl(ref direct_dist) => {
|
distribution_types::SourceDist::DirectUrl(ref direct_dist) => {
|
||||||
Ok(SourceDist::from_direct_dist(direct_dist))
|
Ok(SourceDist::from_direct_dist(direct_dist, hashes))
|
||||||
}
|
}
|
||||||
distribution_types::SourceDist::Git(ref git_dist) => {
|
distribution_types::SourceDist::Git(ref git_dist) => {
|
||||||
Ok(SourceDist::from_git_dist(git_dist))
|
Ok(SourceDist::from_git_dist(git_dist, hashes))
|
||||||
}
|
}
|
||||||
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, hashes))
|
||||||
}
|
}
|
||||||
distribution_types::SourceDist::Directory(ref directory_dist) => {
|
distribution_types::SourceDist::Directory(ref directory_dist) => {
|
||||||
Ok(SourceDist::from_directory_dist(directory_dist))
|
Ok(SourceDist::from_directory_dist(directory_dist, hashes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_registry_dist(reg_dist: &RegistrySourceDist) -> Result<SourceDist, LockError> {
|
fn from_registry_dist(reg_dist: &RegistrySourceDist) -> Result<SourceDist, LockError> {
|
||||||
// FIXME: Is it guaranteed that there is at least one hash?
|
|
||||||
// If not, we probably need to make this fallible.
|
|
||||||
let url = reg_dist
|
let url = reg_dist
|
||||||
.file
|
.file
|
||||||
.url
|
.url
|
||||||
.to_url()
|
.to_url()
|
||||||
.map_err(LockError::invalid_file_url)?;
|
.map_err(LockError::invalid_file_url)?;
|
||||||
let hash = Hash::from(reg_dist.file.hashes[0].clone());
|
let hash = reg_dist.file.hashes.first().cloned().map(Hash::from);
|
||||||
Ok(SourceDist {
|
Ok(SourceDist { url, hash })
|
||||||
url,
|
|
||||||
hash: Some(hash),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_direct_dist(direct_dist: &DirectUrlSourceDist) -> SourceDist {
|
fn from_direct_dist(direct_dist: &DirectUrlSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||||
SourceDist {
|
SourceDist {
|
||||||
url: direct_dist.url.to_url(),
|
url: direct_dist.url.to_url(),
|
||||||
// TODO: We want a hash for the artifact at the URL.
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
hash: todo!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_git_dist(git_dist: &GitSourceDist) -> SourceDist {
|
fn from_git_dist(git_dist: &GitSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||||
SourceDist {
|
SourceDist {
|
||||||
url: git_dist.url.to_url(),
|
url: git_dist.url.to_url(),
|
||||||
hash: None,
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path_dist(path_dist: &PathSourceDist) -> SourceDist {
|
fn from_path_dist(path_dist: &PathSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||||
SourceDist {
|
SourceDist {
|
||||||
url: path_dist.url.to_url(),
|
url: path_dist.url.to_url(),
|
||||||
hash: None,
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_directory_dist(directory_dist: &DirectorySourceDist) -> SourceDist {
|
fn from_directory_dist(
|
||||||
|
directory_dist: &DirectorySourceDist,
|
||||||
|
hashes: &[HashDigest],
|
||||||
|
) -> SourceDist {
|
||||||
SourceDist {
|
SourceDist {
|
||||||
url: directory_dist.url.to_url(),
|
url: directory_dist.url.to_url(),
|
||||||
hash: None,
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -704,60 +704,59 @@ pub(crate) struct Wheel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wheel {
|
impl Wheel {
|
||||||
fn from_resolved_dist(resolved_dist: &ResolvedDist) -> Result<Option<Wheel>, LockError> {
|
fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> Result<Option<Wheel>, LockError> {
|
||||||
match *resolved_dist {
|
match annotated_dist.dist {
|
||||||
// TODO: Do we want to try to lock already-installed distributions?
|
// TODO: Do we want to try to lock already-installed distributions?
|
||||||
// Or should we return an error?
|
// Or should we return an error?
|
||||||
ResolvedDist::Installed(_) => todo!(),
|
ResolvedDist::Installed(_) => todo!(),
|
||||||
ResolvedDist::Installable(ref dist) => Wheel::from_dist(dist),
|
ResolvedDist::Installable(ref dist) => Wheel::from_dist(dist, &annotated_dist.hashes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_dist(dist: &Dist) -> Result<Option<Wheel>, LockError> {
|
fn from_dist(dist: &Dist, hashes: &[HashDigest]) -> Result<Option<Wheel>, LockError> {
|
||||||
match *dist {
|
match *dist {
|
||||||
Dist::Built(ref built_dist) => Wheel::from_built_dist(built_dist).map(Some),
|
Dist::Built(ref built_dist) => Wheel::from_built_dist(built_dist, hashes).map(Some),
|
||||||
Dist::Source(_) => Ok(None),
|
Dist::Source(_) => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_built_dist(built_dist: &BuiltDist) -> Result<Wheel, LockError> {
|
fn from_built_dist(built_dist: &BuiltDist, hashes: &[HashDigest]) -> Result<Wheel, LockError> {
|
||||||
match *built_dist {
|
match *built_dist {
|
||||||
BuiltDist::Registry(ref reg_dist) => Wheel::from_registry_dist(reg_dist),
|
BuiltDist::Registry(ref reg_dist) => Wheel::from_registry_dist(reg_dist),
|
||||||
BuiltDist::DirectUrl(ref direct_dist) => Ok(Wheel::from_direct_dist(direct_dist)),
|
BuiltDist::DirectUrl(ref direct_dist) => {
|
||||||
BuiltDist::Path(ref path_dist) => Ok(Wheel::from_path_dist(path_dist)),
|
Ok(Wheel::from_direct_dist(direct_dist, hashes))
|
||||||
|
}
|
||||||
|
BuiltDist::Path(ref path_dist) => Ok(Wheel::from_path_dist(path_dist, hashes)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_registry_dist(reg_dist: &RegistryBuiltDist) -> Result<Wheel, LockError> {
|
fn from_registry_dist(reg_dist: &RegistryBuiltDist) -> Result<Wheel, LockError> {
|
||||||
// FIXME: Is it guaranteed that there is at least one hash?
|
|
||||||
// If not, we probably need to make this fallible.
|
|
||||||
let filename = reg_dist.filename.clone();
|
let filename = reg_dist.filename.clone();
|
||||||
let url = reg_dist
|
let url = reg_dist
|
||||||
.file
|
.file
|
||||||
.url
|
.url
|
||||||
.to_url()
|
.to_url()
|
||||||
.map_err(LockError::invalid_file_url)?;
|
.map_err(LockError::invalid_file_url)?;
|
||||||
let hash = Hash::from(reg_dist.file.hashes[0].clone());
|
let hash = reg_dist.file.hashes.first().cloned().map(Hash::from);
|
||||||
Ok(Wheel {
|
Ok(Wheel {
|
||||||
url,
|
url,
|
||||||
hash: Some(hash),
|
hash,
|
||||||
filename,
|
filename,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist) -> Wheel {
|
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
||||||
Wheel {
|
Wheel {
|
||||||
url: direct_dist.url.to_url(),
|
url: direct_dist.url.to_url(),
|
||||||
// TODO: We want a hash for the artifact at the URL.
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
hash: todo!(),
|
|
||||||
filename: direct_dist.filename.clone(),
|
filename: direct_dist.filename.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path_dist(path_dist: &PathBuiltDist) -> Wheel {
|
fn from_path_dist(path_dist: &PathBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
||||||
Wheel {
|
Wheel {
|
||||||
url: path_dist.url.to_url(),
|
url: path_dist.url.to_url(),
|
||||||
hash: None,
|
hash: hashes.first().cloned().map(Hash::from),
|
||||||
filename: path_dist.filename.clone(),
|
filename: path_dist.filename.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -816,8 +815,8 @@ pub(crate) struct Dependency {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dependency {
|
impl Dependency {
|
||||||
fn from_resolved_dist(resolved_dist: &ResolvedDist) -> Dependency {
|
fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> Dependency {
|
||||||
let id = DistributionId::from_resolved_dist(resolved_dist);
|
let id = DistributionId::from_annotated_dist(annotated_dist);
|
||||||
Dependency { id }
|
Dependency { id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::hash::BuildHasherDefault;
|
use std::hash::BuildHasherDefault;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ use distribution_types::{
|
||||||
use once_map::OnceMap;
|
use once_map::OnceMap;
|
||||||
use pep440_rs::{Version, VersionSpecifier};
|
use pep440_rs::{Version, VersionSpecifier};
|
||||||
use pep508_rs::MarkerEnvironment;
|
use pep508_rs::MarkerEnvironment;
|
||||||
use pypi_types::HashDigest;
|
use pypi_types::{HashDigest, Metadata23};
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
|
|
@ -47,14 +48,40 @@ pub enum AnnotationStyle {
|
||||||
Split,
|
Split,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pinned package with its resolved distribution and metadata. The [`ResolvedDist`] refers to a
|
||||||
|
/// specific distribution (e.g., a specific wheel), while the [`Metadata23`] refers to the metadata
|
||||||
|
/// for the package-version pair.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AnnotatedDist {
|
||||||
|
pub dist: ResolvedDist,
|
||||||
|
pub hashes: Vec<HashDigest>,
|
||||||
|
pub metadata: Metadata23,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Name for AnnotatedDist {
|
||||||
|
fn name(&self) -> &PackageName {
|
||||||
|
self.dist.name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DistributionMetadata for AnnotatedDist {
|
||||||
|
fn version_or_url(&self) -> VersionOrUrlRef {
|
||||||
|
self.dist.version_or_url()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AnnotatedDist {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(&self.dist, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A complete resolution graph in which every node represents a pinned package and every edge
|
/// A complete resolution graph in which every node represents a pinned package and every edge
|
||||||
/// represents a dependency between two pinned packages.
|
/// represents a dependency between two pinned packages.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ResolutionGraph {
|
pub struct ResolutionGraph {
|
||||||
/// The underlying graph.
|
/// The underlying graph.
|
||||||
petgraph: petgraph::graph::Graph<ResolvedDist, Range<Version>, petgraph::Directed>,
|
petgraph: petgraph::graph::Graph<AnnotatedDist, Range<Version>, petgraph::Directed>,
|
||||||
/// The metadata for every distribution in this resolution.
|
|
||||||
hashes: FxHashMap<PackageName, Vec<HashDigest>>,
|
|
||||||
/// The enabled extras for every distribution in this resolution.
|
/// The enabled extras for every distribution in this resolution.
|
||||||
extras: FxHashMap<PackageName, Vec<ExtraName>>,
|
extras: FxHashMap<PackageName, Vec<ExtraName>>,
|
||||||
/// The set of editable requirements in this resolution.
|
/// The set of editable requirements in this resolution.
|
||||||
|
|
@ -186,21 +213,18 @@ impl ResolutionGraph {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add every package to the graph.
|
||||||
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
||||||
// write our own graph, given that our requirements are so simple.
|
// write our own graph, given that our requirements are so simple.
|
||||||
let mut petgraph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len());
|
let mut petgraph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len());
|
||||||
let mut hashes =
|
|
||||||
FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default());
|
|
||||||
|
|
||||||
// Add every package to the graph.
|
|
||||||
let mut inverse =
|
let mut inverse =
|
||||||
FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default());
|
FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default());
|
||||||
|
|
||||||
for (package, version) in selection {
|
for (package, version) in selection {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Package(package_name, None, None) => {
|
PubGrubPackage::Package(package_name, None, None) => {
|
||||||
// Create the distribution.
|
// Create the distribution.
|
||||||
let pinned_package = if let Some((editable, _, _)) = editables.get(package_name)
|
let dist = if let Some((editable, _, _)) = editables.get(package_name) {
|
||||||
{
|
|
||||||
Dist::from_editable(package_name.clone(), editable.clone())?.into()
|
Dist::from_editable(package_name.clone(), editable.clone())?.into()
|
||||||
} else {
|
} else {
|
||||||
pins.get(package_name, version)
|
pins.get(package_name, version)
|
||||||
|
|
@ -208,57 +232,117 @@ impl ResolutionGraph {
|
||||||
.clone()
|
.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add its hashes to the index, preserving those that were already present in
|
// Extract the hashes, preserving those that were already present in the
|
||||||
// the lockfile if necessary.
|
// lockfile if necessary.
|
||||||
if let Some(digests) = preferences
|
let hashes = if let Some(digests) = preferences
|
||||||
.match_hashes(package_name, version)
|
.match_hashes(package_name, version)
|
||||||
.filter(|digests| !digests.is_empty())
|
.filter(|digests| !digests.is_empty())
|
||||||
{
|
{
|
||||||
hashes.insert(package_name.clone(), digests.to_vec());
|
digests.to_vec()
|
||||||
} else if let Some(versions_response) = packages.get(package_name) {
|
} else if let Some(versions_response) = packages.get(package_name) {
|
||||||
if let VersionsResponse::Found(ref version_maps) = *versions_response {
|
if let VersionsResponse::Found(ref version_maps) = *versions_response {
|
||||||
for version_map in version_maps {
|
version_maps
|
||||||
if let Some(mut digests) = version_map.hashes(version) {
|
.iter()
|
||||||
|
.find_map(|version_map| version_map.hashes(version))
|
||||||
|
.map(|mut digests| {
|
||||||
digests.sort_unstable();
|
digests.sort_unstable();
|
||||||
hashes.insert(package_name.clone(), digests);
|
digests
|
||||||
break;
|
})
|
||||||
}
|
.unwrap_or_default()
|
||||||
}
|
} else {
|
||||||
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract the metadata.
|
||||||
|
let metadata = if let Some((_, metadata, _)) = editables.get(package_name) {
|
||||||
|
metadata.clone()
|
||||||
|
} else {
|
||||||
|
let dist = PubGrubDistribution::from_registry(package_name, version);
|
||||||
|
|
||||||
|
let response = distributions.get(&dist.version_id()).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Every package should have metadata: {:?}",
|
||||||
|
dist.version_id()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let MetadataResponse::Found(archive) = &*response else {
|
||||||
|
panic!(
|
||||||
|
"Every package should have metadata: {:?}",
|
||||||
|
dist.version_id()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
archive.metadata.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Add the distribution to the graph.
|
// Add the distribution to the graph.
|
||||||
let index = petgraph.add_node(pinned_package);
|
let index = petgraph.add_node(AnnotatedDist {
|
||||||
|
dist,
|
||||||
|
hashes,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
}
|
}
|
||||||
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
||||||
// Create the distribution.
|
// Create the distribution.
|
||||||
let pinned_package = if let Some((editable, _, _)) = editables.get(package_name)
|
let dist = if let Some((editable, _, _)) = editables.get(package_name) {
|
||||||
{
|
|
||||||
Dist::from_editable(package_name.clone(), editable.clone())?
|
Dist::from_editable(package_name.clone(), editable.clone())?
|
||||||
} else {
|
} else {
|
||||||
Dist::from_url(package_name.clone(), url_to_precise(url.clone()))?
|
Dist::from_url(package_name.clone(), url_to_precise(url.clone()))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add its hashes to the index, preserving those that were already present in
|
// Extract the hashes, preserving those that were already present in the
|
||||||
// the lockfile if necessary.
|
// lockfile if necessary.
|
||||||
if let Some(digests) = preferences
|
let hashes = if let Some(digests) = preferences
|
||||||
.match_hashes(package_name, version)
|
.match_hashes(package_name, version)
|
||||||
.filter(|digests| !digests.is_empty())
|
.filter(|digests| !digests.is_empty())
|
||||||
{
|
{
|
||||||
hashes.insert(package_name.clone(), digests.to_vec());
|
digests.to_vec()
|
||||||
} else if let Some(metadata_response) =
|
} else if let Some(metadata_response) = distributions.get(&dist.version_id()) {
|
||||||
distributions.get(&pinned_package.version_id())
|
|
||||||
{
|
|
||||||
if let MetadataResponse::Found(ref archive) = *metadata_response {
|
if let MetadataResponse::Found(ref archive) = *metadata_response {
|
||||||
let mut digests = archive.hashes.clone();
|
let mut digests = archive.hashes.clone();
|
||||||
digests.sort_unstable();
|
digests.sort_unstable();
|
||||||
hashes.insert(package_name.clone(), digests);
|
digests
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract the metadata.
|
||||||
|
let metadata = if let Some((_, metadata, _)) = editables.get(package_name) {
|
||||||
|
metadata.clone()
|
||||||
|
} else {
|
||||||
|
let dist = PubGrubDistribution::from_url(package_name, url);
|
||||||
|
|
||||||
|
let response = distributions.get(&dist.version_id()).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Every package should have metadata: {:?}",
|
||||||
|
dist.version_id()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let MetadataResponse::Found(archive) = &*response else {
|
||||||
|
panic!(
|
||||||
|
"Every package should have metadata: {:?}",
|
||||||
|
dist.version_id()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
archive.metadata.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Add the distribution to the graph.
|
// Add the distribution to the graph.
|
||||||
let index = petgraph.add_node(pinned_package.into());
|
let index = petgraph.add_node(AnnotatedDist {
|
||||||
|
dist: dist.into(),
|
||||||
|
hashes,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -311,7 +395,6 @@ impl ResolutionGraph {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
petgraph,
|
petgraph,
|
||||||
hashes,
|
|
||||||
extras,
|
extras,
|
||||||
editables,
|
editables,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
|
|
@ -341,7 +424,7 @@ impl ResolutionGraph {
|
||||||
.into_nodes_edges()
|
.into_nodes_edges()
|
||||||
.0
|
.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|node| node.weight)
|
.map(|node| node.weight.dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`Diagnostic`]s that were encountered while building the graph.
|
/// Return the [`Diagnostic`]s that were encountered while building the graph.
|
||||||
|
|
@ -352,7 +435,7 @@ impl ResolutionGraph {
|
||||||
/// Return the underlying graph.
|
/// Return the underlying graph.
|
||||||
pub fn petgraph(
|
pub fn petgraph(
|
||||||
&self,
|
&self,
|
||||||
) -> &petgraph::graph::Graph<ResolvedDist, Range<Version>, petgraph::Directed> {
|
) -> &petgraph::graph::Graph<AnnotatedDist, Range<Version>, petgraph::Directed> {
|
||||||
&self.petgraph
|
&self.petgraph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,7 +589,7 @@ impl ResolutionGraph {
|
||||||
let mut locked_dists = vec![];
|
let mut locked_dists = vec![];
|
||||||
for node_index in self.petgraph.node_indices() {
|
for node_index in self.petgraph.node_indices() {
|
||||||
let dist = &self.petgraph[node_index];
|
let dist = &self.petgraph[node_index];
|
||||||
let mut locked_dist = lock::Distribution::from_resolved_dist(dist)?;
|
let mut locked_dist = lock::Distribution::from_annotated_dist(dist)?;
|
||||||
for edge in self.petgraph.neighbors(node_index) {
|
for edge in self.petgraph.neighbors(node_index) {
|
||||||
let dependency_dist = &self.petgraph[edge];
|
let dependency_dist = &self.petgraph[edge];
|
||||||
locked_dist.add_dependency(dependency_dist);
|
locked_dist.add_dependency(dependency_dist);
|
||||||
|
|
@ -586,9 +669,9 @@ impl<'a> DisplayResolutionGraph<'a> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Node<'a> {
|
enum Node<'a> {
|
||||||
/// A node linked to an editable distribution.
|
/// A node linked to an editable distribution.
|
||||||
Editable(&'a PackageName, &'a LocalEditable),
|
Editable(&'a LocalEditable),
|
||||||
/// A node linked to a non-editable distribution.
|
/// A node linked to a non-editable distribution.
|
||||||
Distribution(&'a PackageName, &'a ResolvedDist, &'a [ExtraName]),
|
Distribution(&'a PackageName, &'a AnnotatedDist, &'a [ExtraName]),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
|
@ -600,18 +683,10 @@ enum NodeKey<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Node<'a> {
|
impl<'a> Node<'a> {
|
||||||
/// Return the name of the package.
|
|
||||||
fn name(&self) -> &'a PackageName {
|
|
||||||
match self {
|
|
||||||
Node::Editable(name, _) => name,
|
|
||||||
Node::Distribution(name, _, _) => name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a comparable key for the node.
|
/// Return a comparable key for the node.
|
||||||
fn key(&self) -> NodeKey<'a> {
|
fn key(&self) -> NodeKey<'a> {
|
||||||
match self {
|
match self {
|
||||||
Node::Editable(_, editable) => NodeKey::Editable(editable.verbatim()),
|
Node::Editable(editable) => NodeKey::Editable(editable.verbatim()),
|
||||||
Node::Distribution(name, _, _) => NodeKey::Distribution(name),
|
Node::Distribution(name, _, _) => NodeKey::Distribution(name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -619,8 +694,8 @@ impl<'a> Node<'a> {
|
||||||
/// Return the [`IndexUrl`] of the distribution, if any.
|
/// Return the [`IndexUrl`] of the distribution, if any.
|
||||||
fn index(&self) -> Option<&IndexUrl> {
|
fn index(&self) -> Option<&IndexUrl> {
|
||||||
match self {
|
match self {
|
||||||
Node::Editable(_, _) => None,
|
Node::Editable(_) => None,
|
||||||
Node::Distribution(_, dist, _) => dist.index(),
|
Node::Distribution(_, package, _) => package.dist.index(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -628,7 +703,7 @@ impl<'a> Node<'a> {
|
||||||
impl Verbatim for Node<'_> {
|
impl Verbatim for Node<'_> {
|
||||||
fn verbatim(&self) -> Cow<'_, str> {
|
fn verbatim(&self) -> Cow<'_, str> {
|
||||||
match self {
|
match self {
|
||||||
Node::Editable(_, editable) => Cow::Owned(format!("-e {}", editable.verbatim())),
|
Node::Editable(editable) => Cow::Owned(format!("-e {}", editable.verbatim())),
|
||||||
Node::Distribution(_, dist, &[]) => dist.verbatim(),
|
Node::Distribution(_, dist, &[]) => dist.verbatim(),
|
||||||
Node::Distribution(_, dist, extras) => {
|
Node::Distribution(_, dist, extras) => {
|
||||||
let mut extras = extras.to_vec();
|
let mut extras = extras.to_vec();
|
||||||
|
|
@ -661,7 +736,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = if let Some((editable, _, _)) = self.resolution.editables.get(name) {
|
let node = if let Some((editable, _, _)) = self.resolution.editables.get(name) {
|
||||||
Node::Editable(name, editable)
|
Node::Editable(editable)
|
||||||
} else if self.include_extras {
|
} else if self.include_extras {
|
||||||
Node::Distribution(
|
Node::Distribution(
|
||||||
name,
|
name,
|
||||||
|
|
@ -689,13 +764,8 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
// Display the distribution hashes, if any.
|
// Display the distribution hashes, if any.
|
||||||
let mut has_hashes = false;
|
let mut has_hashes = false;
|
||||||
if self.show_hashes {
|
if self.show_hashes {
|
||||||
if let Some(hashes) = self
|
if let Node::Distribution(_, package, _) = node {
|
||||||
.resolution
|
for hash in &package.hashes {
|
||||||
.hashes
|
|
||||||
.get(node.name())
|
|
||||||
.filter(|hashes| !hashes.is_empty())
|
|
||||||
{
|
|
||||||
for hash in hashes {
|
|
||||||
has_hashes = true;
|
has_hashes = true;
|
||||||
line.push_str(" \\\n");
|
line.push_str(" \\\n");
|
||||||
line.push_str(" --hash=");
|
line.push_str(" --hash=");
|
||||||
|
|
@ -722,7 +792,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
// Include all external sources (e.g., requirements files).
|
// Include all external sources (e.g., requirements files).
|
||||||
let default = BTreeSet::default();
|
let default = BTreeSet::default();
|
||||||
let source = match node {
|
let source = match node {
|
||||||
Node::Editable(_, editable) => {
|
Node::Editable(editable) => {
|
||||||
self.sources.get_editable(&editable.url).unwrap_or(&default)
|
self.sources.get_editable(&editable.url).unwrap_or(&default)
|
||||||
}
|
}
|
||||||
Node::Distribution(name, _, _) => self.sources.get(name).unwrap_or(&default),
|
Node::Distribution(name, _, _) => self.sources.get(name).unwrap_or(&default),
|
||||||
|
|
@ -810,7 +880,7 @@ impl From<ResolutionGraph> for distribution_types::Resolution {
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
(
|
(
|
||||||
graph.petgraph[node].name().clone(),
|
graph.petgraph[node].name().clone(),
|
||||||
graph.petgraph[node].clone(),
|
graph.petgraph[node].dist.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
|
||||||
|
|
@ -963,8 +963,8 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
||||||
// If we're excluding transitive dependencies, short-circuit.
|
// If we're excluding transitive dependencies, short-circuit.
|
||||||
if self.dependency_mode.is_direct() {
|
if self.dependency_mode.is_direct() {
|
||||||
// If an extra is provided, wait for the metadata to be available, since it's
|
// If an extra is provided, wait for the metadata to be available, since it's
|
||||||
// still required for reporting diagnostics.
|
// still required for generating the lock file.
|
||||||
if extra.is_some() && !self.editables.contains(package_name) {
|
if !self.editables.contains(package_name) {
|
||||||
// Determine the distribution to lookup.
|
// Determine the distribution to lookup.
|
||||||
let dist = match url {
|
let dist = match url {
|
||||||
Some(url) => PubGrubDistribution::from_url(package_name, url),
|
Some(url) => PubGrubDistribution::from_url(package_name, url),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue