uv_distribution/index/
built_wheel_index.rs1use std::borrow::Cow;
2
3use uv_cache::{Cache, CacheBucket, CacheShard, WheelCache};
4use uv_cache_info::CacheInfo;
5use uv_distribution_types::{
6 BuildInfo, BuildVariables, ConfigSettings, DirectUrlSourceDist, DirectorySourceDist,
7 ExtraBuildRequirement, ExtraBuildRequires, ExtraBuildVariables, GitSourceDist, Hashed,
8 PackageConfigSettings, PathSourceDist,
9};
10use uv_normalize::PackageName;
11use uv_platform_tags::Tags;
12use uv_pypi_types::HashDigests;
13use uv_types::HashStrategy;
14
15use crate::Error;
16use crate::index::cached_wheel::{CachedWheel, ResolvedWheel};
17use crate::source::{HTTP_REVISION, HttpRevisionPointer, LOCAL_REVISION, LocalRevisionPointer};
18
19#[derive(Debug)]
21pub struct BuiltWheelIndex<'a> {
22 cache: &'a Cache,
23 tags: &'a Tags,
24 hasher: &'a HashStrategy,
25 config_settings: &'a ConfigSettings,
26 config_settings_package: &'a PackageConfigSettings,
27 extra_build_requires: &'a ExtraBuildRequires,
28 extra_build_variables: &'a ExtraBuildVariables,
29}
30
31impl<'a> BuiltWheelIndex<'a> {
32 pub fn new(
34 cache: &'a Cache,
35 tags: &'a Tags,
36 hasher: &'a HashStrategy,
37 config_settings: &'a ConfigSettings,
38 config_settings_package: &'a PackageConfigSettings,
39 extra_build_requires: &'a ExtraBuildRequires,
40 extra_build_variables: &'a ExtraBuildVariables,
41 ) -> Self {
42 Self {
43 cache,
44 tags,
45 hasher,
46 config_settings,
47 config_settings_package,
48 extra_build_requires,
49 extra_build_variables,
50 }
51 }
52
53 pub fn url(&self, source_dist: &DirectUrlSourceDist) -> Result<Option<CachedWheel>, Error> {
58 let cache_shard = self.cache.shard(
60 CacheBucket::SourceDistributions,
61 WheelCache::Url(source_dist.url.raw()).root(),
62 );
63
64 let Some(pointer) = HttpRevisionPointer::read_from(cache_shard.entry(HTTP_REVISION))?
66 else {
67 return Ok(None);
68 };
69
70 let revision = pointer.into_revision();
72 if !revision.satisfies(self.hasher.get(source_dist)) {
73 return Ok(None);
74 }
75
76 let cache_shard = cache_shard.shard(revision.id());
77
78 let config_settings = self.config_settings_for(&source_dist.name);
80 let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
81 let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
82 let build_info =
83 BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
84 let cache_shard = build_info
85 .cache_shard()
86 .map(|digest| cache_shard.shard(digest))
87 .unwrap_or(cache_shard);
88
89 Ok(self.find(&cache_shard).map(|wheel| {
90 CachedWheel::from_entry(
91 wheel,
92 revision.into_hashes(),
93 CacheInfo::default(),
94 build_info,
95 )
96 }))
97 }
98
99 pub fn path(&self, source_dist: &PathSourceDist) -> Result<Option<CachedWheel>, Error> {
101 let cache_shard = self.cache.shard(
102 CacheBucket::SourceDistributions,
103 WheelCache::Path(&source_dist.url).root(),
104 );
105
106 let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))?
108 else {
109 return Ok(None);
110 };
111
112 let cache_info =
114 CacheInfo::from_file(&source_dist.install_path).map_err(Error::CacheRead)?;
115 if cache_info != *pointer.cache_info() {
116 return Ok(None);
117 }
118
119 let revision = pointer.into_revision();
121 if !revision.satisfies(self.hasher.get(source_dist)) {
122 return Ok(None);
123 }
124
125 let cache_shard = cache_shard.shard(revision.id());
126
127 let config_settings = self.config_settings_for(&source_dist.name);
129 let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
130 let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
131 let build_info =
132 BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
133 let cache_shard = build_info
134 .cache_shard()
135 .map(|digest| cache_shard.shard(digest))
136 .unwrap_or(cache_shard);
137
138 Ok(self.find(&cache_shard).map(|wheel| {
139 CachedWheel::from_entry(wheel, revision.into_hashes(), cache_info, build_info)
140 }))
141 }
142
143 pub fn directory(
146 &self,
147 source_dist: &DirectorySourceDist,
148 ) -> Result<Option<CachedWheel>, Error> {
149 let cache_shard = self.cache.shard(
150 CacheBucket::SourceDistributions,
151 if source_dist.editable.unwrap_or(false) {
152 WheelCache::Editable(&source_dist.url).root()
153 } else {
154 WheelCache::Path(&source_dist.url).root()
155 },
156 );
157
158 let Some(pointer) = LocalRevisionPointer::read_from(cache_shard.entry(LOCAL_REVISION))?
160 else {
161 return Ok(None);
162 };
163
164 let cache_info = CacheInfo::from_directory(&source_dist.install_path)?;
166 if cache_info != *pointer.cache_info() {
167 return Ok(None);
168 }
169
170 let revision = pointer.into_revision();
172 if !revision.satisfies(self.hasher.get(source_dist)) {
173 return Ok(None);
174 }
175
176 let cache_shard = cache_shard.shard(revision.id());
177
178 let config_settings = self.config_settings_for(&source_dist.name);
180 let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
181 let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
182 let build_info =
183 BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
184 let cache_shard = build_info
185 .cache_shard()
186 .map(|digest| cache_shard.shard(digest))
187 .unwrap_or(cache_shard);
188
189 Ok(self.find(&cache_shard).map(|wheel| {
190 CachedWheel::from_entry(wheel, revision.into_hashes(), cache_info, build_info)
191 }))
192 }
193
194 pub fn git(&self, source_dist: &GitSourceDist) -> Option<CachedWheel> {
196 if self.hasher.get(source_dist).is_validate() {
198 return None;
199 }
200
201 let git_sha = source_dist.git.precise()?;
202
203 let cache_shard = self.cache.shard(
204 CacheBucket::SourceDistributions,
205 WheelCache::Git(&source_dist.url, git_sha.as_short_str()).root(),
206 );
207
208 let config_settings = self.config_settings_for(&source_dist.name);
210 let extra_build_deps = self.extra_build_requires_for(&source_dist.name);
211 let extra_build_vars = self.extra_build_variables_for(&source_dist.name);
212 let build_info =
213 BuildInfo::from_settings(&config_settings, extra_build_deps, extra_build_vars);
214 let cache_shard = build_info
215 .cache_shard()
216 .map(|digest| cache_shard.shard(digest))
217 .unwrap_or(cache_shard);
218
219 self.find(&cache_shard).map(|wheel| {
220 CachedWheel::from_entry(
221 wheel,
222 HashDigests::empty(),
223 CacheInfo::default(),
224 build_info,
225 )
226 })
227 }
228
229 fn find(&self, shard: &CacheShard) -> Option<ResolvedWheel> {
246 let mut candidate: Option<ResolvedWheel> = None;
247
248 for wheel_dir in uv_fs::entries(shard).ok().into_iter().flatten() {
250 if wheel_dir
252 .extension()
253 .is_some_and(|ext| ext.eq_ignore_ascii_case("lock"))
254 {
255 continue;
256 }
257
258 match ResolvedWheel::from_built_source(&wheel_dir, self.cache) {
259 None => {}
260 Some(dist_info) => {
261 let compatibility = dist_info.filename.compatibility(self.tags);
263
264 if !compatibility.is_compatible() {
266 continue;
267 }
268
269 if let Some(existing) = candidate.as_ref() {
270 if dist_info.filename.version > existing.filename.version
272 || compatibility > existing.filename.compatibility(self.tags)
273 {
274 candidate = Some(dist_info);
275 }
276 } else {
277 candidate = Some(dist_info);
278 }
279 }
280 }
281 }
282
283 candidate
284 }
285
286 fn config_settings_for(&self, name: &PackageName) -> Cow<'_, ConfigSettings> {
288 if let Some(package_settings) = self.config_settings_package.get(name) {
289 Cow::Owned(package_settings.clone().merge(self.config_settings.clone()))
290 } else {
291 Cow::Borrowed(self.config_settings)
292 }
293 }
294
295 fn extra_build_requires_for(&self, name: &PackageName) -> &[ExtraBuildRequirement] {
297 self.extra_build_requires
298 .get(name)
299 .map(Vec::as_slice)
300 .unwrap_or(&[])
301 }
302
303 fn extra_build_variables_for(&self, name: &PackageName) -> Option<&BuildVariables> {
305 self.extra_build_variables.get(name)
306 }
307}