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