1use std::future::Future;
2use std::sync::Arc;
3pub use uv_resolver_types::MetadataResponse;
4pub(crate) use uv_resolver_types::MetadataUnavailable;
5
6use uv_client::MetadataFormat;
7use uv_configuration::BuildOptions;
8use uv_distribution::{DistributionDatabase, Reporter};
9use uv_distribution_types::{
10 Dist, File, IndexCapabilities, IndexLocations, IndexMetadata, IndexMetadataRef, IndexUrl,
11 InstalledDist, MinimumLibcVersion, RequestedDist, RequiresPython,
12};
13use uv_normalize::PackageName;
14use uv_platform_tags::Tags;
15use uv_static::EnvVars;
16use uv_types::{BuildContext, HashStrategy};
17
18use crate::ExcludeNewer;
19use crate::flat_index::{FlatDistributions, FlatIndex};
20use crate::version_map::VersionMap;
21use crate::yanks::AllowedYanks;
22
23pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
24pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>;
25
26#[derive(Debug)]
32pub enum VariantsJsonResponse {
33 Found(Vec<u8>),
35 Unavailable(MetadataUnavailable),
37}
38
39#[derive(Debug)]
41pub enum VersionsResponse {
42 Found(Vec<VersionMap>),
44 NotFound,
46 NoIndex,
48 Offline,
50}
51
52pub trait ResolverProvider {
53 fn get_package_versions<'io>(
55 &'io self,
56 package_name: &'io PackageName,
57 index: Option<&'io IndexMetadata>,
58 ) -> impl Future<Output = PackageVersionsResult> + 'io;
59
60 fn get_or_build_wheel_metadata<'io>(
66 &'io self,
67 dist: &'io Dist,
68 ) -> impl Future<Output = WheelMetadataResult> + 'io;
69
70 fn get_installed_metadata<'io>(
72 &'io self,
73 dist: &'io InstalledDist,
74 ) -> impl Future<Output = WheelMetadataResult> + 'io;
75
76 fn fetch_variants_json<'io>(
78 &'io self,
79 package_name: &'io PackageName,
80 file: &'io File,
81 index: &'io IndexUrl,
82 ) -> impl Future<Output = Result<VariantsJsonResponse, uv_client::Error>> + 'io;
83
84 #[must_use]
86 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self;
87}
88
89pub struct DefaultResolverProvider<'a, Context: BuildContext> {
92 fetcher: DistributionDatabase<'a, Context>,
94 flat_index: &'a FlatIndex,
96 tags: Option<Tags>,
97 requires_python: RequiresPython,
98 allowed_yanks: AllowedYanks,
99 hasher: &'a HashStrategy,
100 exclude_newer: ExcludeNewer,
101 available_version_cutoff: Option<jiff::Timestamp>,
102 index_locations: &'a IndexLocations,
103 build_options: &'a BuildOptions,
104 capabilities: &'a IndexCapabilities,
105 minimum_libc_version: Option<MinimumLibcVersion>,
106}
107
108impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
109 pub fn new(
111 fetcher: DistributionDatabase<'a, Context>,
112 flat_index: &'a FlatIndex,
113 tags: Option<&'a Tags>,
114 requires_python: &'a RequiresPython,
115 allowed_yanks: AllowedYanks,
116 hasher: &'a HashStrategy,
117 exclude_newer: ExcludeNewer,
118 index_locations: &'a IndexLocations,
119 build_options: &'a BuildOptions,
120 capabilities: &'a IndexCapabilities,
121 minimum_libc_version: Option<MinimumLibcVersion>,
122 ) -> Self {
123 Self {
124 fetcher,
125 flat_index,
126 tags: tags.cloned(),
127 requires_python: requires_python.clone(),
128 allowed_yanks,
129 hasher,
130 exclude_newer,
131 available_version_cutoff: std::env::var(
132 EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF,
133 )
134 .ok()
135 .and_then(|value| value.parse().ok()),
136 index_locations,
137 build_options,
138 capabilities,
139 minimum_libc_version,
140 }
141 }
142
143 fn effective_exclude_newer(
144 &self,
145 package_name: &PackageName,
146 index: &uv_distribution_types::IndexUrl,
147 ) -> Option<jiff::Timestamp> {
148 self.exclude_newer.exclude_newer_package_for_index(
149 package_name,
150 self.index_locations.exclude_newer_for(index),
151 )
152 }
153
154 fn classify_metadata_error(
156 &self,
157 error: uv_client::Error,
158 index: Option<&IndexUrl>,
159 ) -> Result<MetadataUnavailable, uv_client::Error> {
160 let retries = error.retries();
161 let duration = error.duration();
162 match error.into_kind() {
163 uv_client::ErrorKind::Offline(_) => Ok(MetadataUnavailable::Offline),
164 uv_client::ErrorKind::MetadataParseError(_, _, error) => {
165 Ok(MetadataUnavailable::InvalidMetadata(Arc::new(*error)))
166 }
167 uv_client::ErrorKind::Metadata(_, error) => {
168 Ok(MetadataUnavailable::InvalidStructure(Arc::new(error)))
169 }
170 kind => {
171 if let uv_client::ErrorKind::WrappedReqwestError(_, error) = &kind
172 && let Some(status) = error.status()
173 && index.is_some_and(|index| {
174 self.index_locations.ignores_error_code_for(index, status)
175 })
176 {
177 Ok(MetadataUnavailable::Network(status))
178 } else {
179 Err(uv_client::Error::new(kind, retries, duration))
180 }
181 }
182 }
183 }
184}
185
186impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Context> {
187 async fn get_package_versions<'io>(
189 &'io self,
190 package_name: &'io PackageName,
191 index: Option<&'io IndexMetadata>,
192 ) -> PackageVersionsResult {
193 let result = self
194 .fetcher
195 .client()
196 .manual(|client, semaphore| {
197 client.simple_detail(
198 package_name,
199 index.map(IndexMetadataRef::from),
200 self.capabilities,
201 semaphore,
202 )
203 })
204 .await;
205
206 let flat_index = index.is_none().then_some(self.flat_index);
208 let flat_distributions = flat_index
209 .and_then(|flat_index| flat_index.get(package_name))
210 .map(|entries| {
211 FlatDistributions::from_entries(
212 entries.iter().cloned(),
213 self.tags.as_ref(),
214 self.hasher,
215 self.build_options,
216 self.minimum_libc_version,
217 )
218 });
219
220 match result {
221 Ok(results) => Ok(VersionsResponse::Found(
222 results
223 .into_iter()
224 .map(|(index, metadata)| {
225 let included_version_cutoff =
226 self.effective_exclude_newer(package_name, index);
227 let available_version_cutoff = included_version_cutoff
228 .is_none()
229 .then_some(self.available_version_cutoff)
230 .flatten();
231
232 match metadata {
233 MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
234 metadata,
235 package_name,
236 index.clone(),
237 self.tags.clone(),
238 self.requires_python.clone(),
239 self.allowed_yanks.clone(),
240 self.hasher.clone(),
241 included_version_cutoff,
242 available_version_cutoff,
243 flat_distributions.clone(),
244 self.build_options,
245 self.minimum_libc_version,
246 ),
247 MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
248 metadata,
249 self.tags.as_ref(),
250 self.hasher,
251 self.build_options,
252 self.minimum_libc_version,
253 ),
254 }
255 })
256 .collect(),
257 )),
258 Err(err) => match err.kind() {
259 uv_client::ErrorKind::RemotePackageNotFound(_) => {
260 if let Some(flat_index) = flat_distributions {
261 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
262 } else {
263 Ok(VersionsResponse::NotFound)
264 }
265 }
266 uv_client::ErrorKind::NoIndex(_) => {
267 if let Some(flat_index) = flat_distributions {
268 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
269 } else if flat_index.is_some_and(FlatIndex::offline) {
270 Ok(VersionsResponse::Offline)
271 } else {
272 Ok(VersionsResponse::NoIndex)
273 }
274 }
275 uv_client::ErrorKind::Offline(_) => {
276 if let Some(flat_index) = flat_distributions {
277 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
278 } else {
279 Ok(VersionsResponse::Offline)
280 }
281 }
282 _ => Err(err),
283 },
284 }
285 }
286
287 async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult {
289 match self
290 .fetcher
291 .get_or_build_wheel_metadata(dist, self.hasher.metadata_policy(dist))
292 .await
293 {
294 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
295 Err(err) => match err {
296 uv_distribution::Error::Client(client) => {
297 match self.classify_metadata_error(client, dist.index()) {
300 Ok(reason) => Ok(MetadataResponse::Unavailable(reason)),
301 Err(error) => Err(error.into()),
302 }
303 }
304 uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
305 Ok(MetadataResponse::Unavailable(
306 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
307 ))
308 }
309 uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
310 Ok(MetadataResponse::Unavailable(
311 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
312 ))
313 }
314 uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
315 MetadataUnavailable::InvalidMetadata(Arc::new(err)),
316 )),
317 uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
318 MetadataUnavailable::InvalidStructure(Arc::new(*err)),
319 )),
320 uv_distribution::Error::RequiresPython(requires_python, version) => {
321 Ok(MetadataResponse::Unavailable(
322 MetadataUnavailable::RequiresPython(requires_python, version),
323 ))
324 }
325 err => Ok(MetadataResponse::Error(
326 Box::new(RequestedDist::Installable(dist.clone())),
327 Arc::new(err),
328 )),
329 },
330 }
331 }
332
333 async fn get_installed_metadata<'io>(
335 &'io self,
336 dist: &'io InstalledDist,
337 ) -> WheelMetadataResult {
338 match self.fetcher.get_installed_metadata(dist).await {
339 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
340 Err(err) => Ok(MetadataResponse::Error(
341 Box::new(RequestedDist::Installed(dist.clone())),
342 Arc::new(err),
343 )),
344 }
345 }
346
347 async fn fetch_variants_json<'io>(
348 &'io self,
349 package_name: &'io PackageName,
350 file: &'io File,
351 index: &'io IndexUrl,
352 ) -> Result<VariantsJsonResponse, uv_client::Error> {
353 match self
354 .fetcher
355 .client()
356 .managed(|client| client.fetch_variants_json(package_name, file, index))
357 .await
358 {
359 Ok(contents) => Ok(VariantsJsonResponse::Found(contents)),
360 Err(error) => match self.classify_metadata_error(error, Some(index)) {
361 Ok(reason) => Ok(VariantsJsonResponse::Unavailable(reason)),
362 Err(error) => Err(error),
363 },
364 }
365 }
366
367 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
369 Self {
370 fetcher: self.fetcher.with_reporter(reporter),
371 ..self
372 }
373 }
374}