mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Only store compatible wheels in the resolver (#109)
Rather than constantly iterating over all files and testing their compatibility with the current platform, just store wheels we can actually consider in the solver cache.
This commit is contained in:
parent
5f5788e866
commit
1b433fdcee
3 changed files with 101 additions and 67 deletions
|
@ -2,7 +2,7 @@ use platform_host::{Arch, Os, Platform, PlatformError};
|
||||||
|
|
||||||
/// A set of compatible tags for a given Python version and platform, in
|
/// A set of compatible tags for a given Python version and platform, in
|
||||||
/// (`python_tag`, `abi_tag`, `platform_tag`) format.
|
/// (`python_tag`, `abi_tag`, `platform_tag`) format.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Tags(Vec<(String, String, String)>);
|
pub struct Tags(Vec<(String, String, String)>);
|
||||||
|
|
||||||
impl Tags {
|
impl Tags {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
|
use puffin_package::package_name::PackageName;
|
||||||
|
|
||||||
use crate::pubgrub::package::PubGrubPackage;
|
use crate::pubgrub::package::PubGrubPackage;
|
||||||
use crate::pubgrub::version::PubGrubVersion;
|
use crate::pubgrub::version::PubGrubVersion;
|
||||||
|
@ -13,6 +14,9 @@ pub enum ResolveError {
|
||||||
#[error("The request stream terminated unexpectedly")]
|
#[error("The request stream terminated unexpectedly")]
|
||||||
StreamTermination,
|
StreamTermination,
|
||||||
|
|
||||||
|
#[error("No platform-compatible distributions found for: {0}")]
|
||||||
|
NoCompatibleDistributions(PackageName),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Client(#[from] puffin_client::PypiClientError),
|
Client(#[from] puffin_client::PypiClientError),
|
||||||
|
|
||||||
|
|
|
@ -63,23 +63,20 @@ impl<'a> Resolver<'a> {
|
||||||
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
|
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
|
||||||
let (request_sink, request_stream) = futures::channel::mpsc::unbounded();
|
let (request_sink, request_stream) = futures::channel::mpsc::unbounded();
|
||||||
let requests_fut = tokio::spawn({
|
let requests_fut = tokio::spawn({
|
||||||
|
let tags = self.tags.clone();
|
||||||
let cache = cache.clone();
|
let cache = cache.clone();
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
async move {
|
async move {
|
||||||
let mut response_stream = request_stream
|
let mut response_stream = request_stream
|
||||||
.map({
|
.map({
|
||||||
|request: Request| match request {
|
|request: Request| match request {
|
||||||
Request::Package(package_name) => Either::Left(
|
Request::Package(package_name) => {
|
||||||
client
|
Either::Left(client.simple(package_name.clone()).map_ok(
|
||||||
// TODO(charlie): Remove this clone.
|
move |metadata| Response::Package(package_name, metadata),
|
||||||
.simple(package_name.clone())
|
))
|
||||||
.map_ok(move |metadata| {
|
}
|
||||||
Response::Package(package_name, metadata)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Request::Version(file) => Either::Right(
|
Request::Version(file) => Either::Right(
|
||||||
client
|
client
|
||||||
// TODO(charlie): Remove this clone.
|
|
||||||
.file(file.clone())
|
.file(file.clone())
|
||||||
.map_ok(move |metadata| Response::Version(file, metadata)),
|
.map_ok(move |metadata| Response::Version(file, metadata)),
|
||||||
),
|
),
|
||||||
|
@ -93,7 +90,49 @@ impl<'a> Resolver<'a> {
|
||||||
match response? {
|
match response? {
|
||||||
Response::Package(package_name, metadata) => {
|
Response::Package(package_name, metadata) => {
|
||||||
trace!("Received package metadata for {}", package_name);
|
trace!("Received package metadata for {}", package_name);
|
||||||
cache.packages.insert(package_name.clone(), metadata);
|
|
||||||
|
// Only bother storing platform-compatible wheels.
|
||||||
|
let wheels: Vec<Wheel> = metadata
|
||||||
|
.files
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|file| {
|
||||||
|
let Ok(filename) =
|
||||||
|
WheelFilename::from_str(file.filename.as_str())
|
||||||
|
else {
|
||||||
|
debug!("Ignoring non-wheel: {}", file.filename);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(version) =
|
||||||
|
pep440_rs::Version::from_str(&filename.version)
|
||||||
|
else {
|
||||||
|
debug!("Ignoring invalid version: {}", file.filename);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !filename.is_compatible(&tags) {
|
||||||
|
debug!(
|
||||||
|
"Ignoring wheel with incompatible tags: {}",
|
||||||
|
file.filename
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Wheel {
|
||||||
|
name: PackageName::normalize(&filename.distribution),
|
||||||
|
version,
|
||||||
|
file,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if wheels.is_empty() {
|
||||||
|
return Err(ResolveError::NoCompatibleDistributions(
|
||||||
|
package_name,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.packages.insert(package_name.clone(), wheels);
|
||||||
}
|
}
|
||||||
Response::Version(file, metadata) => {
|
Response::Version(file, metadata) => {
|
||||||
trace!("Received file metadata for {}", file.filename);
|
trace!("Received file metadata for {}", file.filename);
|
||||||
|
@ -184,6 +223,8 @@ impl<'a> Resolver<'a> {
|
||||||
// Pick the next compatible version.
|
// Pick the next compatible version.
|
||||||
let version = match decision.1 {
|
let version = match decision.1 {
|
||||||
None => {
|
None => {
|
||||||
|
debug!("No compatible version found for: {}", next);
|
||||||
|
|
||||||
let term_intersection = state
|
let term_intersection = state
|
||||||
.partial_solution
|
.partial_solution
|
||||||
.term_intersection_for_package(&next)
|
.term_intersection_for_package(&next)
|
||||||
|
@ -300,35 +341,18 @@ impl<'a> Resolver<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find a compatible version.
|
// Find a compatible version.
|
||||||
let simple_json = entry.value();
|
let wheels = entry.value();
|
||||||
let Some(file) = simple_json.files.iter().rev().find(|file| {
|
let Some(wheel) = wheels.iter().rev().find(|wheel| {
|
||||||
let Ok(name) = WheelFilename::from_str(file.filename.as_str()) else {
|
range
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(version) = pep440_rs::Version::from_str(&name.version) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !name.is_compatible(self.tags) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !range
|
|
||||||
.borrow()
|
.borrow()
|
||||||
.contains(&PubGrubVersion::from(version.clone()))
|
.contains(&PubGrubVersion::from(wheel.version.clone()))
|
||||||
{
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
true
|
|
||||||
}) else {
|
}) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Emit a request to fetch the metadata for this version.
|
// Emit a request to fetch the metadata for this version.
|
||||||
if in_flight.insert(file.hashes.sha256.clone()) {
|
if in_flight.insert(wheel.file.hashes.sha256.clone()) {
|
||||||
request_sink.unbounded_send(Request::Version(file.clone()))?;
|
request_sink.unbounded_send(Request::Version(wheel.file.clone()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
selection = index;
|
selection = index;
|
||||||
|
@ -346,49 +370,45 @@ impl<'a> Resolver<'a> {
|
||||||
// TODO(charlie): Ideally, we'd choose the first package for which metadata is
|
// TODO(charlie): Ideally, we'd choose the first package for which metadata is
|
||||||
// available.
|
// available.
|
||||||
let entry = cache.packages.wait(package_name).await.unwrap();
|
let entry = cache.packages.wait(package_name).await.unwrap();
|
||||||
let simple_json = entry.value();
|
let wheels = entry.value();
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Searching for a compatible version of {} ({})",
|
||||||
|
package_name,
|
||||||
|
range.borrow()
|
||||||
|
);
|
||||||
|
|
||||||
// Find a compatible version.
|
// Find a compatible version.
|
||||||
let name_version_file = simple_json.files.iter().rev().find_map(|file| {
|
let wheel = wheels.iter().rev().find(|wheel| {
|
||||||
let Ok(name) = WheelFilename::from_str(file.filename.as_str()) else {
|
if range
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(version) = pep440_rs::Version::from_str(&name.version) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !name.is_compatible(self.tags) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !range
|
|
||||||
.borrow()
|
.borrow()
|
||||||
.contains(&PubGrubVersion::from(version.clone()))
|
.contains(&PubGrubVersion::from(wheel.version.clone()))
|
||||||
{
|
{
|
||||||
return None;
|
true
|
||||||
};
|
} else {
|
||||||
|
debug!("Ignoring non-satisfying version: {}", wheel.version);
|
||||||
Some((package_name.clone(), version.clone(), file.clone()))
|
false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((name, version, file)) = name_version_file {
|
if let Some(wheel) = wheel {
|
||||||
debug!("Selecting: {}=={} ({})", name, version, file.filename);
|
debug!(
|
||||||
|
"Selecting: {}=={} ({})",
|
||||||
|
wheel.name, wheel.version, wheel.file.filename
|
||||||
|
);
|
||||||
|
|
||||||
// We want to return a package pinned to a specific version; but we _also_ want to
|
// We want to return a package pinned to a specific version; but we _also_ want to
|
||||||
// store the exact file that we selected to satisfy that version.
|
// store the exact file that we selected to satisfy that version.
|
||||||
pins.entry(name)
|
pins.entry(wheel.name.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(version.clone(), file.clone());
|
.insert(wheel.version.clone(), wheel.file.clone());
|
||||||
|
|
||||||
// Emit a request to fetch the metadata for this version.
|
// Emit a request to fetch the metadata for this version.
|
||||||
if cache.versions.get(&file.hashes.sha256).is_none() {
|
if in_flight.insert(wheel.file.hashes.sha256.clone()) {
|
||||||
if in_flight.insert(file.hashes.sha256.clone()) {
|
request_sink.unbounded_send(Request::Version(wheel.file.clone()))?;
|
||||||
request_sink.unbounded_send(Request::Version(file.clone()))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((package, Some(PubGrubVersion::from(version))))
|
Ok((package, Some(PubGrubVersion::from(wheel.version.clone()))))
|
||||||
} else {
|
} else {
|
||||||
// We have metadata for the package, but no compatible version.
|
// We have metadata for the package, but no compatible version.
|
||||||
Ok((package, None))
|
Ok((package, None))
|
||||||
|
@ -426,9 +446,9 @@ impl<'a> Resolver<'a> {
|
||||||
}
|
}
|
||||||
PubGrubPackage::Package(package_name, extra) => {
|
PubGrubPackage::Package(package_name, extra) => {
|
||||||
if let Some(extra) = extra.as_ref() {
|
if let Some(extra) = extra.as_ref() {
|
||||||
debug!("Fetching dependencies for {}[{:?}]", package_name, extra);
|
debug!("Fetching dependencies for: {}[{:?}]", package_name, extra);
|
||||||
} else {
|
} else {
|
||||||
debug!("Fetching dependencies for {}", package_name);
|
debug!("Fetching dependencies for: {}", package_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the metadata to be available.
|
// Wait for the metadata to be available.
|
||||||
|
@ -497,9 +517,19 @@ enum Response {
|
||||||
Version(File, Metadata21),
|
Version(File, Metadata21),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Wheel {
|
||||||
|
/// The underlying [`File`] for this wheel.
|
||||||
|
file: File,
|
||||||
|
/// The normalized name of the package.
|
||||||
|
name: PackageName,
|
||||||
|
/// The version of the package.
|
||||||
|
version: pep440_rs::Version,
|
||||||
|
}
|
||||||
|
|
||||||
struct SolverCache {
|
struct SolverCache {
|
||||||
/// A map from package name to the metadata for that package.
|
/// A map from package name to the wheels available for that package.
|
||||||
packages: WaitMap<PackageName, SimpleJson>,
|
packages: WaitMap<PackageName, Vec<Wheel>>,
|
||||||
|
|
||||||
/// A map from wheel SHA to the metadata for that wheel.
|
/// A map from wheel SHA to the metadata for that wheel.
|
||||||
versions: WaitMap<String, Metadata21>,
|
versions: WaitMap<String, Metadata21>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue