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 fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
55 self.index.get(package_name)
56 }
57
58 pub 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 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 fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
89 self.0.iter()
90 }
91
92 pub fn remove(&mut self, version: &Version) -> Option<PrioritizedDist> {
94 self.0.remove(version)
95 }
96
97 fn add_file(
99 &mut self,
100 file: File,
101 filename: DistFilename,
102 tags: Option<&Tags>,
103 hasher: &HashStrategy,
104 build_options: &BuildOptions,
105 index: IndexUrl,
106 ) {
107 match filename {
110 DistFilename::WheelFilename(filename) => {
111 let version = filename.version.clone();
112
113 let compatibility = Self::wheel_compatibility(
114 &filename,
115 file.hashes.as_slice(),
116 tags,
117 hasher,
118 build_options,
119 );
120 let dist = RegistryBuiltWheel {
121 filename,
122 file: Box::new(file),
123 index,
124 };
125 match self.0.entry(version) {
126 Entry::Occupied(mut entry) => {
127 entry.get_mut().insert_built(dist, vec![], compatibility);
128 }
129 Entry::Vacant(entry) => {
130 entry.insert(PrioritizedDist::from_built(dist, vec![], compatibility));
131 }
132 }
133 }
134 DistFilename::SourceDistFilename(filename) => {
135 let compatibility = Self::source_dist_compatibility(
136 &filename,
137 file.hashes.as_slice(),
138 hasher,
139 build_options,
140 );
141 let dist = RegistrySourceDist {
142 name: filename.name.clone(),
143 version: filename.version.clone(),
144 ext: filename.extension,
145 file: Box::new(file),
146 index,
147 wheels: vec![],
148 };
149 match self.0.entry(filename.version) {
150 Entry::Occupied(mut entry) => {
151 entry.get_mut().insert_source(dist, vec![], compatibility);
152 }
153 Entry::Vacant(entry) => {
154 entry.insert(PrioritizedDist::from_source(dist, vec![], compatibility));
155 }
156 }
157 }
158 }
159 }
160
161 fn source_dist_compatibility(
162 filename: &SourceDistFilename,
163 hashes: &[HashDigest],
164 hasher: &HashStrategy,
165 build_options: &BuildOptions,
166 ) -> SourceDistCompatibility {
167 if build_options.no_build_package(&filename.name) {
169 return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
170 }
171
172 let hash_policy = hasher.get_package(&filename.name, &filename.version);
174 let hash = if hash_policy.requires_validation() {
175 if hashes.is_empty() {
176 HashComparison::Missing
177 } else if hash_policy.matches(hashes) {
178 HashComparison::Matched
179 } else {
180 HashComparison::Mismatched
181 }
182 } else {
183 HashComparison::Matched
184 };
185
186 SourceDistCompatibility::Compatible(hash)
187 }
188
189 fn wheel_compatibility(
190 filename: &WheelFilename,
191 hashes: &[HashDigest],
192 tags: Option<&Tags>,
193 hasher: &HashStrategy,
194 build_options: &BuildOptions,
195 ) -> WheelCompatibility {
196 if build_options.no_binary_package(&filename.name) {
198 return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
199 }
200
201 let priority = match tags {
203 Some(tags) => match filename.compatibility(tags) {
204 TagCompatibility::Incompatible(tag) => {
205 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag));
206 }
207 TagCompatibility::Compatible(priority) => Some(priority),
208 },
209 None => None,
210 };
211
212 let hash_policy = hasher.get_package(&filename.name, &filename.version);
214 let hash = if hash_policy.requires_validation() {
215 if hashes.is_empty() {
216 HashComparison::Missing
217 } else if hash_policy.matches(hashes) {
218 HashComparison::Matched
219 } else {
220 HashComparison::Mismatched
221 }
222 } else {
223 HashComparison::Matched
224 };
225
226 let build_tag = filename.build_tag().cloned();
228
229 WheelCompatibility::Compatible(hash, priority, build_tag)
230 }
231}
232
233impl IntoIterator for FlatDistributions {
234 type Item = (Version, PrioritizedDist);
235 type IntoIter = std::collections::btree_map::IntoIter<Version, PrioritizedDist>;
236
237 fn into_iter(self) -> Self::IntoIter {
238 self.0.into_iter()
239 }
240}
241
242impl From<FlatDistributions> for BTreeMap<Version, PrioritizedDist> {
243 fn from(distributions: FlatDistributions) -> Self {
244 distributions.0
245 }
246}
247
248impl From<BTreeMap<Version, PrioritizedDist>> for FlatDistributions {
250 fn from(distributions: BTreeMap<Version, PrioritizedDist>) -> Self {
251 Self(distributions)
252 }
253}