1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SupportedToolchain {
20 pub versions: &'static [&'static str],
23 pub header_digest: &'static str,
25 pub missing_symbols: &'static [&'static str],
28}
29
30impl SupportedToolchain {
31 #[must_use]
34 pub fn includes(&self, version: &str) -> bool {
35 self.versions.contains(&version)
36 }
37}
38
39pub const SUPPORTED_TOOLCHAINS: &[SupportedToolchain] = &[
52 SupportedToolchain {
53 versions: &["4.26.0"],
54 header_digest: "e0ea3efaccceb5b75c7e9e1ab92952c8aa85c3faee28ee949dfeb8ab428ad218",
55 missing_symbols: &[],
56 },
57 SupportedToolchain {
58 versions: &["4.27.0"],
59 header_digest: "42255d180910bb063d97c87cfb2a61550009ca9ceb6f495069c56bfaa6c92e13",
60 missing_symbols: &[],
61 },
62 SupportedToolchain {
63 versions: &["4.28.0"],
64 header_digest: "624726e5f1f10fd77cd95b8fe8f30389312e57c8fc98e6c2f1989289bdb5fb0e",
65 missing_symbols: &[],
66 },
67 SupportedToolchain {
68 versions: &["4.28.1"],
69 header_digest: "648ecfb615ef0222cd63b5f1bbbc379a06749bc0f5f4c2eb16ffca26fd18fe81",
70 missing_symbols: &[],
71 },
72 SupportedToolchain {
73 versions: &["4.29.0"],
74 header_digest: "671683950ef412474bede2c6a2b50aecf4f99bc29e1ddaf2222ee54ad4ffb91c",
75 missing_symbols: &[],
76 },
77 SupportedToolchain {
78 versions: &["4.29.1"],
79 header_digest: "2e481a0dac7215eb16123eaef97298ae5a6d0bd0c28c534c2818e2d2f2a28efc",
80 missing_symbols: &[],
81 },
82 SupportedToolchain {
83 versions: &["4.30.0"],
84 header_digest: "5a25125970f4f1dcf85a4c403463b387a8ff93535cd4a3054cafdee1759017d7",
85 missing_symbols: &[],
86 },
87 SupportedToolchain {
88 versions: &["4.31.0-rc1", "4.31.0-rc2"],
89 header_digest: "99ef35d69709e38caf836cf9ebbdf94d4474801e04157b8a72622dbdc653ec87",
90 missing_symbols: &[],
91 },
92 SupportedToolchain {
93 versions: &["4.31.0"],
94 header_digest: "486fe204404c0fdfb753b7e089c1c0d38fbdb396206030497696165e31218992",
95 missing_symbols: &[],
96 },
97 SupportedToolchain {
98 versions: &["4.32.0-rc1", "4.32.0"],
99 header_digest: "22eed50aa703c4403010fabc12a7231ffa34dc979bd59ca1bfbac13c29a1dad2",
100 missing_symbols: &[],
101 },
102];
103
104#[must_use]
106pub fn supported_for(version: &str) -> Option<&'static SupportedToolchain> {
107 SUPPORTED_TOOLCHAINS.iter().find(|t| t.includes(version))
108}
109
110#[must_use]
113pub fn supported_by_digest(digest: &str) -> Option<&'static SupportedToolchain> {
114 SUPPORTED_TOOLCHAINS.iter().find(|t| t.header_digest == digest)
115}
116
117#[must_use]
121pub fn symbol_present_in_window(symbol: &str) -> bool {
122 SUPPORTED_TOOLCHAINS
123 .iter()
124 .all(|t| !t.missing_symbols.contains(&symbol))
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn precedence_key(version: &str) -> (Vec<u64>, bool, &str) {
139 let (core, pre) = match version.split_once('-') {
140 Some((core, pre)) => (core, pre),
141 None => (version, ""),
142 };
143 let core_nums = core.split('.').map(|n| n.parse().unwrap_or(0)).collect();
144 (core_nums, pre.is_empty(), pre)
145 }
146
147 #[test]
148 fn window_is_non_empty_and_ordered_by_first_version() {
149 assert!(!SUPPORTED_TOOLCHAINS.is_empty());
150 for w in SUPPORTED_TOOLCHAINS.windows(2) {
151 let (Some(prev), Some(next)) = (w.first(), w.get(1)) else {
152 continue;
153 };
154 let (Some(a), Some(b)) = (prev.versions.first(), next.versions.first()) else {
155 continue;
156 };
157 assert!(
158 precedence_key(a) < precedence_key(b),
159 "SUPPORTED_TOOLCHAINS must be sorted ascending by first version: {a} >= {b}",
160 );
161 }
162 }
163
164 #[test]
165 fn every_entry_lists_at_least_one_version() {
166 for t in SUPPORTED_TOOLCHAINS {
167 assert!(
168 !t.versions.is_empty(),
169 "entry with digest {} has no versions",
170 t.header_digest
171 );
172 }
173 }
174
175 #[test]
176 fn digests_are_distinct() {
177 for (i, a) in SUPPORTED_TOOLCHAINS.iter().enumerate() {
178 let Some(rest) = SUPPORTED_TOOLCHAINS.get(i + 1..) else {
179 continue;
180 };
181 for b in rest {
182 assert_ne!(
183 a.header_digest, b.header_digest,
184 "{:?} and {:?} share a header digest \u{2014} merge their `versions` arrays",
185 a.versions, b.versions,
186 );
187 }
188 }
189 }
190
191 #[test]
192 fn versions_are_distinct_across_entries() {
193 let mut seen: Vec<&str> = Vec::new();
194 for t in SUPPORTED_TOOLCHAINS {
195 for &v in t.versions {
196 assert!(
197 !seen.contains(&v),
198 "version {v} appears in more than one SupportedToolchain entry",
199 );
200 seen.push(v);
201 }
202 }
203 }
204
205 #[test]
206 fn digests_are_64_lowercase_hex() {
207 for t in SUPPORTED_TOOLCHAINS {
208 assert_eq!(
209 t.header_digest.len(),
210 64,
211 "entry for {:?}: digest is not 64 chars",
212 t.versions,
213 );
214 assert!(
215 t.header_digest
216 .bytes()
217 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
218 "entry for {:?}: digest is not lowercase hex",
219 t.versions,
220 );
221 }
222 }
223
224 #[test]
225 fn lookups_round_trip() {
226 for t in SUPPORTED_TOOLCHAINS {
227 for &v in t.versions {
228 assert_eq!(supported_for(v), Some(t));
229 }
230 assert_eq!(supported_by_digest(t.header_digest), Some(t));
231 }
232 assert!(supported_for("0.0.0").is_none());
233 assert!(supported_by_digest("0").is_none());
234 }
235
236 #[test]
237 fn fully_present_symbols_pass_window_check() {
238 for &s in crate::REQUIRED_SYMBOLS {
239 assert!(symbol_present_in_window(s), "{s} should be in all supported toolchains");
240 }
241 }
242
243 #[test]
244 fn unknown_symbol_passes_window_check() {
245 assert!(symbol_present_in_window("lean_does_not_exist_zzz"));
249 }
250}