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