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(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
111 .ok()
112 .and_then(|value| value.parse().ok()),
113 index_locations,
114 build_options,
115 capabilities,
116 minimum_libc_version,
117 }
118 }
119
120 fn effective_exclude_newer(
121 &self,
122 package_name: &PackageName,
123 index: &uv_distribution_types::IndexUrl,
124 ) -> Option<jiff::Timestamp> {
125 self.exclude_newer.exclude_newer_package_for_index(
126 package_name,
127 self.index_locations.exclude_newer_for(index),
128 )
129 }
130}
131
132impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Context> {
133 async fn get_package_versions<'io>(
135 &'io self,
136 package_name: &'io PackageName,
137 index: Option<&'io IndexMetadata>,
138 ) -> PackageVersionsResult {
139 let result = self
140 .fetcher
141 .client()
142 .manual(|client, semaphore| {
143 client.simple_detail(
144 package_name,
145 index.map(IndexMetadataRef::from),
146 self.capabilities,
147 semaphore,
148 )
149 })
150 .await;
151
152 let flat_index = index.is_none().then_some(self.flat_index);
154 let flat_distributions = flat_index
155 .and_then(|flat_index| flat_index.get(package_name))
156 .map(|entries| {
157 FlatDistributions::from_entries(
158 entries.iter().cloned(),
159 self.tags.as_ref(),
160 self.hasher,
161 self.build_options,
162 self.minimum_libc_version,
163 )
164 });
165
166 match result {
167 Ok(results) => Ok(VersionsResponse::Found(
168 results
169 .into_iter()
170 .map(|(index, metadata)| {
171 let included_version_cutoff =
172 self.effective_exclude_newer(package_name, index);
173 let available_version_cutoff = included_version_cutoff
174 .is_none()
175 .then_some(self.available_version_cutoff)
176 .flatten();
177
178 match metadata {
179 MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
180 metadata,
181 package_name,
182 index.clone(),
183 self.tags.clone(),
184 self.requires_python.clone(),
185 self.allowed_yanks.clone(),
186 self.hasher.clone(),
187 included_version_cutoff,
188 available_version_cutoff,
189 flat_distributions.clone(),
190 self.build_options,
191 self.minimum_libc_version,
192 ),
193 MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
194 metadata,
195 self.tags.as_ref(),
196 self.hasher,
197 self.build_options,
198 self.minimum_libc_version,
199 ),
200 }
201 })
202 .collect(),
203 )),
204 Err(err) => match err.kind() {
205 uv_client::ErrorKind::RemotePackageNotFound(_) => {
206 if let Some(flat_index) = flat_distributions {
207 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
208 } else {
209 Ok(VersionsResponse::NotFound)
210 }
211 }
212 uv_client::ErrorKind::NoIndex(_) => {
213 if let Some(flat_index) = flat_distributions {
214 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
215 } else if flat_index.is_some_and(FlatIndex::offline) {
216 Ok(VersionsResponse::Offline)
217 } else {
218 Ok(VersionsResponse::NoIndex)
219 }
220 }
221 uv_client::ErrorKind::Offline(_) => {
222 if let Some(flat_index) = flat_distributions {
223 Ok(VersionsResponse::Found(vec![VersionMap::from(flat_index)]))
224 } else {
225 Ok(VersionsResponse::Offline)
226 }
227 }
228 _ => Err(err),
229 },
230 }
231 }
232
233 async fn get_or_build_wheel_metadata<'io>(&'io self, dist: &'io Dist) -> WheelMetadataResult {
235 match self
236 .fetcher
237 .get_or_build_wheel_metadata(dist, self.hasher.metadata_policy(dist))
238 .await
239 {
240 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
241 Err(err) => match err {
242 uv_distribution::Error::Client(client) => {
243 let retries = client.retries();
244 let duration = client.duration();
245 match client.into_kind() {
246 uv_client::ErrorKind::Offline(_) => {
247 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Offline))
248 }
249 uv_client::ErrorKind::MetadataParseError(_, _, err) => {
250 Ok(MetadataResponse::Unavailable(
251 MetadataUnavailable::InvalidMetadata(Arc::new(*err)),
252 ))
253 }
254 uv_client::ErrorKind::Metadata(_, err) => {
255 Ok(MetadataResponse::Unavailable(
256 MetadataUnavailable::InvalidStructure(Arc::new(err)),
257 ))
258 }
259 uv_client::ErrorKind::WrappedReqwestError(url, err) => {
260 let Some(status) = err.status().filter(|status| {
261 dist.index().is_some_and(|index| {
262 self.index_locations.ignores_error_code_for(index, *status)
263 })
264 }) else {
265 return Err(uv_client::Error::new(
266 uv_client::ErrorKind::WrappedReqwestError(url, err),
267 retries,
268 duration,
269 )
270 .into());
271 };
272 Ok(MetadataResponse::Unavailable(MetadataUnavailable::Network(
273 status,
274 )))
275 }
276 kind => Err(uv_client::Error::new(kind, retries, duration).into()),
277 }
278 }
279 uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {
280 Ok(MetadataResponse::Unavailable(
281 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
282 ))
283 }
284 uv_distribution::Error::WheelMetadataNameMismatch { .. } => {
285 Ok(MetadataResponse::Unavailable(
286 MetadataUnavailable::InconsistentMetadata(Arc::new(err)),
287 ))
288 }
289 uv_distribution::Error::Metadata(err) => Ok(MetadataResponse::Unavailable(
290 MetadataUnavailable::InvalidMetadata(Arc::new(err)),
291 )),
292 uv_distribution::Error::WheelMetadata(_, err) => Ok(MetadataResponse::Unavailable(
293 MetadataUnavailable::InvalidStructure(Arc::new(*err)),
294 )),
295 uv_distribution::Error::RequiresPython(requires_python, version) => {
296 Ok(MetadataResponse::Unavailable(
297 MetadataUnavailable::RequiresPython(requires_python, version),
298 ))
299 }
300 err => Ok(MetadataResponse::Error(
301 Box::new(RequestedDist::Installable(dist.clone())),
302 Arc::new(err),
303 )),
304 },
305 }
306 }
307
308 async fn get_installed_metadata<'io>(
310 &'io self,
311 dist: &'io InstalledDist,
312 ) -> WheelMetadataResult {
313 match self.fetcher.get_installed_metadata(dist).await {
314 Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
315 Err(err) => Ok(MetadataResponse::Error(
316 Box::new(RequestedDist::Installed(dist.clone())),
317 Arc::new(err),
318 )),
319 }
320 }
321
322 fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
324 Self {
325 fetcher: self.fetcher.with_reporter(reporter),
326 ..self
327 }
328 }
329}