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, IndexCapabilities, IndexLocations, IndexMetadata, IndexMetadataRef, InstalledDist,
11 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)]
28pub enum VersionsResponse {
29 Found(Vec<VersionMap>),
31 NotFound,
33 NoIndex,
35 Offline,
37}
38
39pub trait ResolverProvider {
40 fn get_package_versions<'io>(
42 &'io self,
43 package_name: &'io PackageName,
44 index: Option<&'io IndexMetadata>,
45 ) -> impl Future<Output = PackageVersionsResult> + 'io;
46
47 fn get_or_build_wheel_metadata<'io>(
53 &'io self,
54 dist: &'io Dist,
55 ) -> impl Future<Output = WheelMetadataResult> + 'io;
56
57 fn get_installed_metadata<'io>(
59 &'io self,
60 dist: &'io InstalledDist,
61 ) -> impl Future<Output = WheelMetadataResult> + 'io;
62
63 #[must_use]
65 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self;
66}
67
68pub struct DefaultResolverProvider<'a, Context: BuildContext> {
71 fetcher: DistributionDatabase<'a, Context>,
73 flat_index: &'a FlatIndex,
75 tags: Option<Tags>,
76 requires_python: RequiresPython,
77 allowed_yanks: AllowedYanks,
78 hasher: &'a HashStrategy,
79 exclude_newer: ExcludeNewer,
80 available_version_cutoff: Option<jiff::Timestamp>,
81 index_locations: &'a IndexLocations,
82 build_options: &'a BuildOptions,
83 capabilities: &'a IndexCapabilities,
84 minimum_libc_version: Option<MinimumLibcVersion>,
85}
86
87impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
88 pub fn new(
90 fetcher: DistributionDatabase<'a, Context>,
91 flat_index: &'a FlatIndex,
92 tags: Option<&'a Tags>,
93 requires_python: &'a RequiresPython,
94 allowed_yanks: AllowedYanks,
95 hasher: &'a HashStrategy,
96 exclude_newer: ExcludeNewer,
97 index_locations: &'a IndexLocations,
98 build_options: &'a BuildOptions,
99 capabilities: &'a IndexCapabilities,
100 minimum_libc_version: Option<MinimumLibcVersion>,
101 ) -> Self {
102 Self {
103 fetcher,
104 flat_index,
105 tags: tags.cloned(),
106 requires_python: requires_python.clone(),
107 allowed_yanks,
108 hasher,
109 exclude_newer,
110 available_version_cutoff: std::env::var(
111 EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF,
112 )
113 .ok()
114 .and_then(|value| value.parse().ok()),
115 index_locations,
116 build_options,
117 capabilities,
118 minimum_libc_version,
119 }
120 }
121
122 fn effective_exclude_newer(
123 &self,
124 package_name: &PackageName,
125 index: &uv_distribution_types::IndexUrl,
126 ) -> Option<jiff::Timestamp> {
127 self.exclude_newer.exclude_newer_package_for_index(
128 package_name,
129 self.index_locations.exclude_newer_for(index),
130 )
131 }
132}
133
134impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Context> {
135 async fn get_package_versions<'io>(
137 &'io self,
138 package_name: &'io PackageName,
139 index: Option<&'io IndexMetadata>,
140 ) -> PackageVersionsResult {
141 let result = self
142 .fetcher
143 .client()
144 .manual(|client, semaphore| {
145 client.simple_detail(
146 package_name,
147 index.map(IndexMetadataRef::from),
148 self.capabilities,
149 semaphore,
150 )
151 })
152 .await;
153
154 let flat_index = index.is_none().then_some(self.flat_index);
156 let flat_distributions = flat_index
157 .and_then(|flat_index| flat_index.get(package_name))
158 .map(|entries| {
159 FlatDistributions::from_entries(
160 entries.iter().cloned(),
161 self.tags.as_ref(),
162 self.hasher,
163 self.build_options,
164 self.minimum_libc_version,
165 )
166 });
167
168 match result {
169 Ok(results) => Ok(VersionsResponse::Found(
170 results
171 .into_iter()
172 .map(|(index, metadata)| {
173 let included_version_cutoff =
174 self.effective_exclude_newer(package_name, index);
175 let available_version_cutoff = included_version_cutoff
176 .is_none()
177 .then_some(self.available_version_cutoff)
178 .flatten();
179
180 match metadata {
181 MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
182 metadata,
183 package_name,
184 index.clone(),
185 self.tags.clone(),
186 self.requires_python.clone(),
187 self.allowed_yanks.clone(),
188 self.hasher.clone(),
189 included_version_cutoff,
190 available_version_cutoff,
191 flat_distributions.clone(),
192 self.build_options,
193 self.minimum_libc_version,
194 ),
195 MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
196 metadata,
197 self.tags.as_ref(),
198 self.hasher,
199 self.build_options,
200 self.minimum_libc_version,
201 ),
202 }
203 })
204 .collect(),
205 )),
206 Err(err) => match err.kind() {
207 uv_client::ErrorKind::RemotePackageNotFound(_) => {
208 if let Some(flat_index) = flat_distributions {
209 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
210 } else {
211 Ok(VersionsResponse::NotFound)
212 }
213 }
214 uv_client::ErrorKind::NoIndex(_) => {
215 if let Some(flat_index) = flat_distributions {
216 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
217 } else if flat_index.is_some_and(FlatIndex::offline) {
218 Ok(VersionsResponse::Offline)
219 } else {
220 Ok(VersionsResponse::NoIndex)
221 }
222 }
223 uv_client::ErrorKind::Offline(_) => {
224 if let Some(flat_index) = flat_distributions {
225 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
226 } else {
227 Ok(VersionsResponse::Offline)
228 }
229 }
230 _ => Err(err),
231 },
232 }
233 }
234
235 async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult {
237 match self
238 .fetcher
239 .get_or_build_wheel_metadata(dist, self.hasher.metadata_policy(dist))
240 .await
241 {
242 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
243 Err(err) => match err {
244 uv_distribution::Error::Client(client) => {
245 let retries = client.retries();
246 let duration = client.duration();
247 match client.into_kind() {
248 uv_client::ErrorKind::Offline(_) => {
249 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline))
250 }
251 uv_client::ErrorKind::MetadataParseError(_, _, err) => {
252 Ok(MetadataResponse::Unavailable(
253 MetadataUnavailable::InvalidMetadata(Arc::new(*err)),
254 ))
255 }
256 uv_client::ErrorKind::Metadata(_, err) => {
257 Ok(MetadataResponse::Unavailable(
258 MetadataUnavailable::InvalidStructure(Arc::new(err)),
259 ))
260 }
261 uv_client::ErrorKind::WrappedReqwestError(url, err) => {
262 let Some(status) = err.status().filter(|status| {
263 dist.index().is_some_and(|index| {
264 self.index_locations.ignores_error_code_for(index, *status)
265 })
266 }) else {
267 return Err(uv_client::Error::new(
268 uv_client::ErrorKind::WrappedReqwestError(url, err),
269 retries,
270 duration,
271 )
272 .into());
273 };
274 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Network(
275 status,
276 )))
277 }
278 kind => Err(uv_client::Error::new(kind, retries, duration).into()),
279 }
280 }
281 uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
282 Ok(MetadataResponse::Unavailable(
283 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
284 ))
285 }
286 uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
287 Ok(MetadataResponse::Unavailable(
288 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
289 ))
290 }
291 uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
292 MetadataUnavailable::InvalidMetadata(Arc::new(err)),
293 )),
294 uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
295 MetadataUnavailable::InvalidStructure(Arc::new(*err)),
296 )),
297 uv_distribution::Error::RequiresPython(requires_python, version) => {
298 Ok(MetadataResponse::Unavailable(
299 MetadataUnavailable::RequiresPython(requires_python, version),
300 ))
301 }
302 err => Ok(MetadataResponse::Error(
303 Box::new(RequestedDist::Installable(dist.clone())),
304 Arc::new(err),
305 )),
306 },
307 }
308 }
309
310 async fn get_installed_metadata<'io>(
312 &'io self,
313 dist: &'io InstalledDist,
314 ) -> WheelMetadataResult {
315 match self.fetcher.get_installed_metadata(dist).await {
316 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
317 Err(err) => Ok(MetadataResponse::Error(
318 Box::new(RequestedDist::Installed(dist.clone())),
319 Arc::new(err),
320 )),
321 }
322 }
323
324 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
326 Self {
327 fetcher: self.fetcher.with_reporter(reporter),
328 ..self
329 }
330 }
331}