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