1use std::collections::BTreeMap;
2use std::collections::btree_map::Entry;
3
4use rustc_hash::FxHashMap;
5use tracing::instrument;
6
7use uv_client::{FlatIndexEntries, FlatIndexEntry};
8use uv_configuration::BuildOptions;
9use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
10use uv_distribution_types::{
11 File, HashComparison, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
12 RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility, WheelCompatibility,
13};
14use uv_normalize::PackageName;
15use uv_pep440::Version;
16use uv_platform_tags::{TagCompatibility, Tags};
17use uv_pypi_types::HashDigest;
18use uv_types::HashStrategy;
19
20#[derive(Debug, Clone, Default)]
23pub struct FlatIndex {
24 index: FxHashMap<PackageName, FlatDistributions>,
26 offline: bool,
29}
30
31impl FlatIndex {
32 #[instrument(skip_all)]
34 pub fn from_entries(
35 entries: FlatIndexEntries,
36 tags: Option<&Tags>,
37 hasher: &HashStrategy,
38 build_options: &BuildOptions,
39 ) -> Self {
40 let mut index = FxHashMap::<PackageName, FlatDistributions>::default();
42 let (entries, offline) = entries.into_parts();
43
44 for entry in entries {
45 let (filename, file, index_url) = entry.into_parts();
46 let distributions = index.entry(filename.name().clone()).or_default();
47 distributions.add_file(file, filename, tags, hasher, build_options, index_url);
48 }
49
50 Self { index, offline }
51 }
52
53 pub(crate) fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
55 self.index.get(package_name)
56 }
57
58 pub(crate) fn offline(&self) -> bool {
61 self.offline
62 }
63}
64
65#[derive(Debug, Clone, Default)]
68pub struct FlatDistributions(BTreeMap<Version, PrioritizedDist>);
69
70impl FlatDistributions {
71 #[instrument(skip_all)]
73 pub(crate) fn from_entries(
74 entries: Vec<FlatIndexEntry>,
75 tags: Option<&Tags>,
76 hasher: &HashStrategy,
77 build_options: &BuildOptions,
78 ) -> Self {
79 let mut distributions = Self::default();
80 for entry in entries {
81 let (filename, file, index) = entry.into_parts();
82 distributions.add_file(file, filename, tags, hasher, build_options, index);
83 }
84 distributions
85 }
86
87 pub(crate) fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
89 self.0.iter()
90 }
91
92 fn add_file(
94 &mut self,
95 file: File,
96 filename: DistFilename,
97 tags: Option<&Tags>,
98 hasher: &HashStrategy,
99 build_options: &BuildOptions,
100 index: IndexUrl,
101 ) {
102 match filename {
105 DistFilename::WheelFilename(filename) => {
106 let version = filename.version.clone();
107
108 let compatibility = Self::wheel_compatibility(
109 &filename,
110 file.hashes.as_slice(),
111 tags,
112 hasher,
113 build_options,
114 );
115 let dist = RegistryBuiltWheel {
116 filename,
117 file: Box::new(file),
118 index,
119 size_is_authoritative: false,
120 };
121 match self.0.entry(version) {
122 Entry::Occupied(mut entry) => {
123 entry.get_mut().insert_built(dist, vec![], compatibility);
124 }
125 Entry::Vacant(entry) => {
126 entry.insert(PrioritizedDist::from_built(dist, vec![], compatibility));
127 }
128 }
129 }
130 DistFilename::SourceDistFilename(filename) => {
131 let compatibility = Self::source_dist_compatibility(
132 &filename,
133 file.hashes.as_slice(),
134 hasher,
135 build_options,
136 );
137 let dist = RegistrySourceDist {
138 name: filename.name.clone(),
139 version: filename.version.clone(),
140 ext: filename.extension,
141 file: Box::new(file),
142 index,
143 wheels: vec![],
144 size_is_authoritative: false,
145 };
146 match self.0.entry(filename.version) {
147 Entry::Occupied(mut entry) => {
148 entry.get_mut().insert_source(dist, vec![], compatibility);
149 }
150 Entry::Vacant(entry) => {
151 entry.insert(PrioritizedDist::from_source(dist, vec![], compatibility));
152 }
153 }
154 }
155 }
156 }
157
158 fn source_dist_compatibility(
159 filename: &SourceDistFilename,
160 hashes: &[HashDigest],
161 hasher: &HashStrategy,
162 build_options: &BuildOptions,
163 ) -> SourceDistCompatibility {
164 if build_options.no_build_package(&filename.name) {
166 return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
167 }
168
169 if !filename.extension.is_pep625_compliant() {
174 return SourceDistCompatibility::Incompatible(IncompatibleSource::NotPep625Filename);
175 }
176
177 let hash_policy = hasher.get_package(&filename.name, &filename.version);
179 let hash = if hash_policy.requires_validation() {
180 if hashes.is_empty() {
181 HashComparison::Missing
182 } else if hash_policy.matches(hashes) {
183 HashComparison::Matched
184 } else {
185 HashComparison::Mismatched
186 }
187 } else {
188 HashComparison::Matched
189 };
190
191 SourceDistCompatibility::Compatible(hash)
192 }
193
194 fn wheel_compatibility(
195 filename: &WheelFilename,
196 hashes: &[HashDigest],
197 tags: Option<&Tags>,
198 hasher: &HashStrategy,
199 build_options: &BuildOptions,
200 ) -> WheelCompatibility {
201 if build_options.no_binary_package(&filename.name) {
203 return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
204 }
205
206 let priority = match tags {
208 Some(tags) => match filename.compatibility(tags) {
209 TagCompatibility::Incompatible(tag) => {
210 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag));
211 }
212 TagCompatibility::Compatible(priority) => Some(priority),
213 },
214 None => None,
215 };
216
217 let hash_policy = hasher.get_package(&filename.name, &filename.version);
219 let hash = if hash_policy.requires_validation() {
220 if hashes.is_empty() {
221 HashComparison::Missing
222 } else if hash_policy.matches(hashes) {
223 HashComparison::Matched
224 } else {
225 HashComparison::Mismatched
226 }
227 } else {
228 HashComparison::Matched
229 };
230
231 let build_tag = filename.build_tag().cloned();
233
234 WheelCompatibility::Compatible(hash, priority, build_tag)
235 }
236}
237
238impl IntoIterator for FlatDistributions {
239 type Item = (Version, PrioritizedDist);
240 type IntoIter = std::collections::btree_map::IntoIter<Version, PrioritizedDist>;
241
242 fn into_iter(self) -> Self::IntoIter {
243 self.0.into_iter()
244 }
245}
246
247impl From<FlatDistributions> for BTreeMap<Version, PrioritizedDist> {
248 fn from(distributions: FlatDistributions) -> Self {
249 distributions.0
250 }
251}
252
253impl From<BTreeMap<Version, PrioritizedDist>> for FlatDistributions {
255 fn from(distributions: BTreeMap<Version, PrioritizedDist>) -> Self {
256 Self(distributions)
257 }
258}