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