use std::borrow::Cow;
use uv_cache::{Cache, CacheBucket, CacheShard, WheelCache};
use uv_cache_info::CacheInfo;
use uv_distribution_types::{
BuildInfo, BuildVariables, ConfigSettings, DirectUrlSourceDist, DirectorySourceDist,
ExtraBuildRequirement, ExtraBuildRequires, ExtraBuildVariables, GitSourceDist, Hashed,
PackageConfigSettings, PathSourceDist,
};
use uv_normalize::PackageName;
use uv_platform_tags::Tags;
use uv_pypi_types::HashDigests;
use uv_types::HashStrategy;
use crate::Error;
use crate::index::cached_wheel::{CachedWheel, ResolvedWheel};
use crate::source::{HTTP_REVISION, HttpRevisionPointer, LOCAL_REVISION, LocalRevisionPointer};
#[derive(Debug)]
pub struct BuiltWheelIndex<'a> {
cache: &'a Cache,
tags: &'a Tags,
hasher: &'a HashStrategy,
config_settings: &'a ConfigSettings,
config_settings_package: &'a PackageConfigSettings,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
}
impl<'a> BuiltWheelIndex<'a> {
pub fn new(
cache: &'a Cache,
tags: &'a Tags,
hasher: &'a HashStrategy,
config_settings: &'a ConfigSettings,
config_settings_package: &'a PackageConfigSettings,
extra_build_requires: &'a ExtraBuildRequires,
extra_build_variables: &'a ExtraBuildVariables,
) -> Self {
Self {
cache,
tags,
hasher,
config_settings,
config_settings_package,
extra_build_requires,
extra_build_variables,
}
}
pub fn url(&self, source_dist: &DirectUrlSourceDist) -> Result<Option<CachedWheel>, Error> {
let cache_shard = self.cache.shard(
CacheBucket::SourceDistributions,
WheelCache::Url(source_dist.url.raw()).root(),
);
let Some(pointer) = HttpRevisionPointer::read_from(cache_shard.entry(HTTP_REVISION))?
else {
return Ok(None);
};
let revision = pointer.into_revision();
if !revision.satisfies(self.hasher.get(source_dist)) {
return Ok(None);
}
let cache_shard = cache_shard.shard(revision.id());
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let build_info =
BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
let cache_shard = build_info
.cache_shard()
.map(|digest| cache_shard.shard(digest))
.unwrap_or(cache_shard);
Ok(self.find(&cache_shard).map(|wheel| {
CachedWheel::from_entry(
wheel,
revision.into_hashes(),
CacheInfo::default(),
build_info,
)
}))
}
pub fn path(&self, source_dist: &PathSourceDist) -> Result<Option<CachedWheel>, Error> {
let cache_shard = self.cache.shard(
CacheBucket::SourceDistributions,
WheelCache::Path(&source_dist.url).root(),
);
let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))?
else {
return Ok(None);
};
let cache_info =
CacheInfo::from_file(&source_dist.install_path).map_err(Error::CacheRead)?;
if cache_info != *pointer.cache_info() {
return Ok(None);
}
let revision = pointer.into_revision();
if !revision.satisfies(self.hasher.get(source_dist)) {
return Ok(None);
}
let cache_shard = cache_shard.shard(revision.id());
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let build_info =
BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
let cache_shard = build_info
.cache_shard()
.map(|digest| cache_shard.shard(digest))
.unwrap_or(cache_shard);
Ok(self.find(&cache_shard).map(|wheel| {
CachedWheel::from_entry(wheel, revision.into_hashes(), cache_info, build_info)
}))
}
pub fn directory(
&self,
source_dist: &DirectorySourceDist,
) -> Result<Option<CachedWheel>, Error> {
let cache_shard = self.cache.shard(
CacheBucket::SourceDistributions,
if source_dist.editable.unwrap_or(false) {
WheelCache::Editable(&source_dist.url).root()
} else {
WheelCache::Path(&source_dist.url).root()
},
);
let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))?
else {
return Ok(None);
};
let cache_info = CacheInfo::from_directory(&source_dist.install_path)?;
if cache_info != *pointer.cache_info() {
return Ok(None);
}
let revision = pointer.into_revision();
if !revision.satisfies(self.hasher.get(source_dist)) {
return Ok(None);
}
let cache_shard = cache_shard.shard(revision.id());
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let build_info =
BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
let cache_shard = build_info
.cache_shard()
.map(|digest| cache_shard.shard(digest))
.unwrap_or(cache_shard);
Ok(self.find(&cache_shard).map(|wheel| {
CachedWheel::from_entry(wheel, revision.into_hashes(), cache_info, build_info)
}))
}
pub fn git(&self, source_dist: &GitSourceDist) -> Option<CachedWheel> {
if self.hasher.get(source_dist).requires_validation() {
return None;
}
let git_sha = source_dist.git.precise()?;
let cache_shard = self.cache.shard(
CacheBucket::SourceDistributions,
WheelCache::Git(&source_dist.url, git_sha.as_short_str()).root(),
);
let config_settings = self.config_settings_for(&source_dist.name);
let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
let build_info =
BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
let cache_shard = build_info
.cache_shard()
.map(|digest| cache_shard.shard(digest))
.unwrap_or(cache_shard);
self.find(&cache_shard).map(|wheel| {
CachedWheel::from_entry(
wheel,
HashDigests::empty(),
CacheInfo::default(),
build_info,
)
})
}
fn find(&self, shard: &CacheShard) -> Option<ResolvedWheel> {
let mut candidate: Option<ResolvedWheel> = None;
for wheel_dir in uv_fs::entries(shard).ok().into_iter().flatten() {
if wheel_dir
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("lock"))
{
continue;
}
match ResolvedWheel::from_built_source(&wheel_dir, self.cache) {
None => {}
Some(dist_info) => {
let compatibility = dist_info.filename.compatibility(self.tags);
if !compatibility.is_compatible() {
continue;
}
if let Some(existing) = candidate.as_ref() {
if dist_info.filename.version > existing.filename.version
|| compatibility > existing.filename.compatibility(self.tags)
{
candidate = Some(dist_info);
}
} else {
candidate = Some(dist_info);
}
}
}
}
candidate
}
fn config_settings_for(&self, name: &PackageName) -> Cow<'_, ConfigSettings> {
if let Some(package_settings) = self.config_settings_package.get(name) {
Cow::Owned(package_settings.clone().merge(self.config_settings.clone()))
} else {
Cow::Borrowed(self.config_settings)
}
}
fn extra_build_requires_for(&self, name: &PackageName) -> &[ExtraBuildRequirement] {
self.extra_build_requires
.get(name)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn extra_build_variables_for(&self, name: &PackageName) -> Option<&BuildVariables> {
self.extra_build_variables.get(name)
}
}