1#[expect(clippy::module_inception)]
23mod config;
24pub mod flags;
25pub mod target_selection;
26#[cfg(test)]
27mod tests;
28pub mod toml;
29
30use std::collections::HashSet;
31use std::path::PathBuf;
32
33pub use config::*;
34use serde::de::Unexpected;
35use serde::{Deserialize, Deserializer};
36use serde_derive::Deserialize;
37pub use target_selection::TargetSelection;
38pub use toml::BUILDER_CONFIG_FILENAME;
39pub use toml::change_id::ChangeId;
40pub use toml::rust::BootstrapOverrideLld;
41pub use toml::target::Target;
42
43use crate::str::FromStr;
44use crate::{Display, exit};
45
46#[macro_export]
48macro_rules! define_config {
49 ($(#[$attr:meta])* struct $name:ident {
50 $(
51 $(#[$field_attr:meta])*
52 $field:ident: Option<$field_ty:ty> = $field_key:literal,
53 )*
54 }) => {
55 $(#[$attr])*
56 pub struct $name {
57 $(
58 $(#[$field_attr])*
59 pub $field: Option<$field_ty>,
60 )*
61 }
62
63 impl Merge for $name {
64 fn merge(
65 &mut self,
66 _parent_config_path: Option<PathBuf>,
67 _included_extensions: &mut HashSet<PathBuf>,
68 other: Self,
69 replace: ReplaceOpt
70 ) {
71 $(
72 match replace {
73 ReplaceOpt::IgnoreDuplicate => {
74 if self.$field.is_none() {
75 self.$field = other.$field;
76 }
77 },
78 ReplaceOpt::Override => {
79 if other.$field.is_some() {
80 self.$field = other.$field;
81 }
82 }
83 ReplaceOpt::ErrorOnDuplicate => {
84 if other.$field.is_some() {
85 if self.$field.is_some() {
86 if cfg!(test) {
87 panic!("overriding existing option")
88 } else {
89 eprintln!("overriding existing option: `{}`", stringify!($field));
90 exit!(2);
91 }
92 } else {
93 self.$field = other.$field;
94 }
95 }
96 }
97 }
98 )*
99 }
100 }
101
102 impl<'de> Deserialize<'de> for $name {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: Deserializer<'de>,
109 {
110 struct Field;
111 impl<'de> serde::de::Visitor<'de> for Field {
112 type Value = $name;
113 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.write_str(concat!("struct ", stringify!($name)))
115 }
116
117 #[inline]
118 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
119 where
120 A: serde::de::MapAccess<'de>,
121 {
122 $(let mut $field: Option<$field_ty> = None;)*
123 while let Some(key) =
124 match serde::de::MapAccess::next_key::<String>(&mut map) {
125 Ok(val) => val,
126 Err(err) => {
127 return Err(err);
128 }
129 }
130 {
131 match &*key {
132 $($field_key => {
133 if $field.is_some() {
134 return Err(<A::Error as serde::de::Error>::duplicate_field(
135 $field_key,
136 ));
137 }
138 $field = match serde::de::MapAccess::next_value::<$field_ty>(
139 &mut map,
140 ) {
141 Ok(val) => Some(val),
142 Err(err) => {
143 return Err(err);
144 }
145 };
146 })*
147 key => {
148 return Err(serde::de::Error::unknown_field(key, FIELDS));
149 }
150 }
151 }
152 Ok($name { $($field),* })
153 }
154 }
155 const FIELDS: &'static [&'static str] = &[
156 $($field_key,)*
157 ];
158 Deserializer::deserialize_struct(
159 deserializer,
160 stringify!($name),
161 FIELDS,
162 Field,
163 )
164 }
165 }
166 }
167}
168
169#[macro_export]
170macro_rules! check_ci_llvm {
171 ($name:expr) => {
172 assert!(
173 $name.is_none(),
174 "setting {} is incompatible with download-ci-llvm.",
175 stringify!($name).replace("_", "-")
176 );
177 };
178}
179
180pub(crate) trait Merge {
181 fn merge(
182 &mut self,
183 parent_config_path: Option<PathBuf>,
184 included_extensions: &mut HashSet<PathBuf>,
185 other: Self,
186 replace: ReplaceOpt,
187 );
188}
189
190impl<T> Merge for Option<T> {
191 fn merge(
192 &mut self,
193 _parent_config_path: Option<PathBuf>,
194 _included_extensions: &mut HashSet<PathBuf>,
195 other: Self,
196 replace: ReplaceOpt,
197 ) {
198 match replace {
199 ReplaceOpt::IgnoreDuplicate => {
200 if self.is_none() {
201 *self = other;
202 }
203 }
204 ReplaceOpt::Override => {
205 if other.is_some() {
206 *self = other;
207 }
208 }
209 ReplaceOpt::ErrorOnDuplicate => {
210 if other.is_some() {
211 if self.is_some() {
212 if cfg!(test) {
213 panic!("overriding existing option")
214 } else {
215 eprintln!("overriding existing option");
216 exit!(2);
217 }
218 } else {
219 *self = other;
220 }
221 }
222 }
223 }
224 }
225}
226
227#[derive(Clone, Debug, Default, Eq, PartialEq)]
228pub enum CompilerBuiltins {
229 #[default]
230 BuildRustOnly,
232 BuildLLVMFuncs,
236 LinkLLVMBuiltinsLib(String),
239}
240
241impl<'de> Deserialize<'de> for CompilerBuiltins {
242 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243 where
244 D: Deserializer<'de>,
245 {
246 Ok(match Deserialize::deserialize(deserializer)? {
247 StringOrBool::Bool(false) => Self::BuildRustOnly,
248 StringOrBool::Bool(true) => Self::BuildLLVMFuncs,
249 StringOrBool::String(path) => Self::LinkLLVMBuiltinsLib(path),
250 })
251 }
252}
253
254#[derive(Copy, Clone, Debug, PartialEq, Eq)]
255pub enum OverrideAllocator {
256 Jemalloc,
257}
258
259impl OverrideAllocator {
260 pub fn feature_name(self) -> &'static str {
261 match self {
262 OverrideAllocator::Jemalloc => "jemalloc",
263 }
264 }
265}
266
267impl<'de> Deserialize<'de> for OverrideAllocator {
268 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
269 where
270 D: Deserializer<'de>,
271 {
272 let name = String::deserialize(deserializer)?;
273 match name.as_str() {
274 "jemalloc" => Ok(Self::Jemalloc),
275 other => Err(serde::de::Error::unknown_variant(other, &["jemalloc"])),
276 }
277 }
278}
279
280#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
281pub enum DebuginfoLevel {
282 #[default]
283 None,
284 LineDirectivesOnly,
285 LineTablesOnly,
286 Limited,
287 Full,
288}
289
290impl<'de> Deserialize<'de> for DebuginfoLevel {
293 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
294 where
295 D: Deserializer<'de>,
296 {
297 use serde::de::Error;
298
299 Ok(match Deserialize::deserialize(deserializer)? {
300 StringOrInt::String(s) if s == "none" => DebuginfoLevel::None,
301 StringOrInt::Int(0) => DebuginfoLevel::None,
302 StringOrInt::String(s) if s == "line-directives-only" => {
303 DebuginfoLevel::LineDirectivesOnly
304 }
305 StringOrInt::String(s) if s == "line-tables-only" => DebuginfoLevel::LineTablesOnly,
306 StringOrInt::String(s) if s == "limited" => DebuginfoLevel::Limited,
307 StringOrInt::Int(1) => DebuginfoLevel::Limited,
308 StringOrInt::String(s) if s == "full" => DebuginfoLevel::Full,
309 StringOrInt::Int(2) => DebuginfoLevel::Full,
310 StringOrInt::Int(n) => {
311 let other = serde::de::Unexpected::Signed(n);
312 return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2"));
313 }
314 StringOrInt::String(s) => {
315 let other = serde::de::Unexpected::Str(&s);
316 return Err(D::Error::invalid_value(
317 other,
318 &"expected none, line-tables-only, limited, or full",
319 ));
320 }
321 })
322 }
323}
324
325impl Display for DebuginfoLevel {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 use DebuginfoLevel::*;
329 f.write_str(match self {
330 None => "0",
331 LineDirectivesOnly => "line-directives-only",
332 LineTablesOnly => "line-tables-only",
333 Limited => "1",
334 Full => "2",
335 })
336 }
337}
338
339#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
340#[serde(untagged)]
341pub enum StringOrBool {
342 String(String),
343 Bool(bool),
344}
345
346impl Default for StringOrBool {
347 fn default() -> StringOrBool {
348 StringOrBool::Bool(false)
349 }
350}
351
352impl StringOrBool {
353 pub fn is_string_or_true(&self) -> bool {
354 matches!(self, Self::String(_) | Self::Bool(true))
355 }
356}
357
358#[derive(Deserialize)]
359#[serde(untagged)]
360pub enum StringOrInt {
361 String(String),
362 Int(i64),
363}
364
365#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
366pub enum LlvmLibunwind {
367 #[default]
368 No,
369 InTree,
370 System,
371}
372
373impl FromStr for LlvmLibunwind {
374 type Err = String;
375
376 fn from_str(value: &str) -> Result<Self, Self::Err> {
377 match value {
378 "no" => Ok(Self::No),
379 "in-tree" => Ok(Self::InTree),
380 "system" => Ok(Self::System),
381 invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")),
382 }
383 }
384}
385
386#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
387pub enum SplitDebuginfo {
388 Packed,
389 Unpacked,
390 #[default]
391 Off,
392}
393
394impl std::str::FromStr for SplitDebuginfo {
395 type Err = ();
396
397 fn from_str(s: &str) -> Result<Self, Self::Err> {
398 match s {
399 "packed" => Ok(SplitDebuginfo::Packed),
400 "unpacked" => Ok(SplitDebuginfo::Unpacked),
401 "off" => Ok(SplitDebuginfo::Off),
402 _ => Err(()),
403 }
404 }
405}
406
407#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
408pub enum CompressDebuginfo {
409 Zlib,
410 #[default]
411 Off,
412}
413
414impl CompressDebuginfo {
415 fn default_on() -> Self {
416 Self::Zlib
417 }
418}
419
420impl std::str::FromStr for CompressDebuginfo {
421 type Err = ();
422
423 fn from_str(s: &str) -> Result<Self, Self::Err> {
424 match s {
425 "zlib" => Ok(CompressDebuginfo::Zlib),
426 "off" => Ok(CompressDebuginfo::Off),
427 _ => Err(()),
428 }
429 }
430}
431
432impl<'de> Deserialize<'de> for CompressDebuginfo {
433 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
434 where
435 D: Deserializer<'de>,
436 {
437 use serde::de::Error;
438
439 Ok(match Deserialize::deserialize(deserializer)? {
440 StringOrBool::Bool(value) => {
441 if value {
442 CompressDebuginfo::default_on()
443 } else {
444 CompressDebuginfo::Off
445 }
446 }
447 StringOrBool::String(value) => CompressDebuginfo::from_str(&value).map_err(|_| {
448 D::Error::invalid_value(Unexpected::Str(&value), &"`zlib` or `off`")
449 })?,
450 })
451 }
452}
453
454#[derive(Copy, Clone, Debug)]
456pub enum ReplaceOpt {
457 IgnoreDuplicate,
459 Override,
461 ErrorOnDuplicate,
463}
464
465#[derive(Clone, Default)]
466pub enum DryRun {
467 #[default]
469 Disabled,
470 SelfCheck,
472 UserSelected,
474}
475
476#[derive(Default, Clone, PartialEq, Debug)]
478pub enum RustcLto {
479 Off,
480 #[default]
481 ThinLocal,
482 Thin,
483 Fat,
484}
485
486impl std::str::FromStr for RustcLto {
487 type Err = String;
488
489 fn from_str(s: &str) -> Result<Self, Self::Err> {
490 match s {
491 "thin-local" => Ok(RustcLto::ThinLocal),
492 "thin" => Ok(RustcLto::Thin),
493 "fat" => Ok(RustcLto::Fat),
494 "off" => Ok(RustcLto::Off),
495 _ => Err(format!("Invalid value for rustc LTO: {s}")),
496 }
497 }
498}
499
500#[derive(Default, Debug, Clone, PartialEq)]
502pub enum GccCiMode {
503 BuildLocally,
505 #[default]
508 DownloadFromCi,
509}
510
511pub fn threads_from_config(v: u32) -> u32 {
512 match v {
513 0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
514 n => n,
515 }
516}