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, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexUrl,
12 PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility,
13 WheelCompatibility,
14};
15use uv_normalize::PackageName;
16use uv_pep440::Version;
17use uv_platform_tags::{TagCompatibility, Tags};
18use uv_pypi_types::HashDigest;
19use uv_types::HashStrategy;
20
21#[derive(Debug, Clone, Default)]
24pub struct FlatIndex {
25 index: FxHashMap<PackageName, FlatDistributions>,
27 offline: bool,
30}
31
32impl FlatIndex {
33 #[instrument(skip_all)]
35 pub fn from_entries(
36 entries: FlatIndexEntries,
37 tags: Option<&Tags>,
38 hasher: &HashStrategy,
39 build_options: &BuildOptions,
40 ) -> Self {
41 let mut index = FxHashMap::<PackageName, FlatDistributions>::default();
43 for entry in entries.entries {
44 let distributions = index.entry(entry.filename.name().clone()).or_default();
45 distributions.add_file(
46 entry.file,
47 entry.filename,
48 tags,
49 hasher,
50 build_options,
51 entry.index,
52 );
53 }
54
55 let offline = entries.offline;
57
58 Self { index, offline }
59 }
60
61 pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
63 self.index.get(package_name)
64 }
65
66 pub fn offline(&self) -> bool {
69 self.offline
70 }
71}
72
73#[derive(Debug, Clone, Default)]
76pub struct FlatDistributions(BTreeMap<Version, PrioritizedDist>);
77
78impl FlatDistributions {
79 #[instrument(skip_all)]
81 pub fn from_entries(
82 entries: Vec<FlatIndexEntry>,
83 tags: Option<&Tags>,
84 hasher: &HashStrategy,
85 build_options: &BuildOptions,
86 ) -> Self {
87 let mut distributions = Self::default();
88 for entry in entries {
89 distributions.add_file(
90 entry.file,
91 entry.filename,
92 tags,
93 hasher,
94 build_options,
95 entry.index,
96 );
97 }
98 distributions
99 }
100
101 pub fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
103 self.0.iter()
104 }
105
106 pub fn remove(&mut self, version: &Version) -> Option<PrioritizedDist> {
108 self.0.remove(version)
109 }
110
111 fn add_file(
113 &mut self,
114 file: File,
115 filename: DistFilename,
116 tags: Option<&Tags>,
117 hasher: &HashStrategy,
118 build_options: &BuildOptions,
119 index: IndexUrl,
120 ) {
121 match filename {
124 DistFilename::WheelFilename(filename) => {
125 let version = filename.version.clone();
126
127 let compatibility = Self::wheel_compatibility(
128 &filename,
129 file.hashes.as_slice(),
130 tags,
131 hasher,
132 build_options,
133 );
134 let dist = RegistryBuiltWheel {
135 filename,
136 file: Box::new(file),
137 index,
138 };
139 match self.0.entry(version) {
140 Entry::Occupied(mut entry) => {
141 entry.get_mut().insert_built(dist, vec![], compatibility);
142 }
143 Entry::Vacant(entry) => {
144 entry.insert(PrioritizedDist::from_built(dist, vec![], compatibility));
145 }
146 }
147 }
148 DistFilename::SourceDistFilename(filename) => {
149 let compatibility = Self::source_dist_compatibility(
150 &filename,
151 file.hashes.as_slice(),
152 hasher,
153 build_options,
154 );
155 let dist = RegistrySourceDist {
156 name: filename.name.clone(),
157 version: filename.version.clone(),
158 ext: filename.extension,
159 file: Box::new(file),
160 index,
161 wheels: vec![],
162 };
163 match self.0.entry(filename.version) {
164 Entry::Occupied(mut entry) => {
165 entry.get_mut().insert_source(dist, vec![], compatibility);
166 }
167 Entry::Vacant(entry) => {
168 entry.insert(PrioritizedDist::from_source(dist, vec![], compatibility));
169 }
170 }
171 }
172 }
173 }
174
175 fn source_dist_compatibility(
176 filename: &SourceDistFilename,
177 hashes: &[HashDigest],
178 hasher: &HashStrategy,
179 build_options: &BuildOptions,
180 ) -> SourceDistCompatibility {
181 if build_options.no_build_package(&filename.name) {
183 return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
184 }
185
186 let hash = if let HashPolicy::Validate(required) =
188 hasher.get_package(&filename.name, &filename.version)
189 {
190 if hashes.is_empty() {
191 HashComparison::Missing
192 } else if required.iter().any(|hash| hashes.contains(hash)) {
193 HashComparison::Matched
194 } else {
195 HashComparison::Mismatched
196 }
197 } else {
198 HashComparison::Matched
199 };
200
201 SourceDistCompatibility::Compatible(hash)
202 }
203
204 fn wheel_compatibility(
205 filename: &WheelFilename,
206 hashes: &[HashDigest],
207 tags: Option<&Tags>,
208 hasher: &HashStrategy,
209 build_options: &BuildOptions,
210 ) -> WheelCompatibility {
211 if build_options.no_binary_package(&filename.name) {
213 return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
214 }
215
216 let priority = match tags {
218 Some(tags) => match filename.compatibility(tags) {
219 TagCompatibility::Incompatible(tag) => {
220 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag));
221 }
222 TagCompatibility::Compatible(priority) => Some(priority),
223 },
224 None => None,
225 };
226
227 let hash = if let HashPolicy::Validate(required) =
229 hasher.get_package(&filename.name, &filename.version)
230 {
231 if hashes.is_empty() {
232 HashComparison::Missing
233 } else if required.iter().any(|hash| hashes.contains(hash)) {
234 HashComparison::Matched
235 } else {
236 HashComparison::Mismatched
237 }
238 } else {
239 HashComparison::Matched
240 };
241
242 let build_tag = filename.build_tag().cloned();
244
245 WheelCompatibility::Compatible(hash, priority, build_tag)
246 }
247}
248
249impl IntoIterator for FlatDistributions {
250 type Item = (Version, PrioritizedDist);
251 type IntoIter = std::collections::btree_map::IntoIter<Version, PrioritizedDist>;
252
253 fn into_iter(self) -> Self::IntoIter {
254 self.0.into_iter()
255 }
256}
257
258impl From<FlatDistributions> for BTreeMap<Version, PrioritizedDist> {
259 fn from(distributions: FlatDistributions) -> Self {
260 distributions.0
261 }
262}
263
264impl From<BTreeMap<Version, PrioritizedDist>> for FlatDistributions {
266 fn from(distributions: BTreeMap<Version, PrioritizedDist>) -> Self {
267 Self(distributions)
268 }
269}