Skip to main content

bootstrap/core/config/
mod.rs

1//! Entry point for the `config` module.
2//!
3//! This module defines two macros:
4//!
5//! - `define_config!`: A declarative macro used instead of `#[derive(Deserialize)]` to reduce
6//!   compile time and binary size, especially for the bootstrap binary.
7//!
8//! - `check_ci_llvm!`: A compile-time assertion macro that ensures certain settings are
9//!   not enabled when `download-ci-llvm` is active.
10//!
11//! A declarative macro is used here in place of a procedural derive macro to minimize
12//! the compile time of the bootstrap process.
13//!
14//! Additionally, this module defines common types, enums, and helper functions used across
15//! various TOML configuration sections in `bootstrap.toml`.
16//!
17//! It provides shared definitions for:
18//! - Data types deserialized from TOML.
19//! - Utility enums for specific configuration options.
20//! - Helper functions for managing configuration values.
21
22#[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// We are using a decl macro instead of a derive proc macro here to reduce the compile time of bootstrap.
48#[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        // The following is a trimmed version of what serde_derive generates. All parts not relevant
104        // for toml deserialization have been removed. This reduces the binary size and improves
105        // compile time of bootstrap.
106        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    // Only build native rust intrinsic compiler functions.
232    BuildRustOnly,
233    // Some intrinsic functions have a C implementation provided by LLVM's
234    // compiler-rt builtins library. Build them from the LLVM source included
235    // with Rust.
236    BuildLLVMFuncs,
237    // Similar to BuildLLVMFuncs, but specify a path to an existing library
238    // containing LLVM's compiler-rt builtins instead of compiling them.
239    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
294// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only
295// deserializes i64, and derive() only generates visit_u64
296impl<'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
329/// Suitable for passing to `-C debuginfo`
330impl 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/// Describes how to handle conflicts in merging two `TomlConfig`
459#[derive(Copy, Clone, Debug)]
460pub enum ReplaceOpt {
461    /// Silently ignore a duplicated value
462    IgnoreDuplicate,
463    /// Override the current value, even if it's `Some`
464    Override,
465    /// Exit with an error on duplicate values
466    ErrorOnDuplicate,
467}
468
469#[derive(Clone, Default)]
470pub enum DryRun {
471    /// This isn't a dry run.
472    #[default]
473    Disabled,
474    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
475    SelfCheck,
476    /// This is a dry run enabled by the `--dry-run` flag.
477    UserSelected,
478}
479
480/// LTO mode used for compiling rustc itself.
481#[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/// Determines how will GCC be provided.
505#[derive(Default, Debug, Clone, PartialEq)]
506pub enum GccCiMode {
507    /// Build GCC from the local `src/gcc` submodule.
508    BuildLocally,
509    /// Try to download GCC from CI.
510    /// If it is not available on CI, it will be built locally instead.
511    #[default]
512    DownloadFromCi,
513}
514
515#[derive(Clone, Debug, PartialEq)]
516pub enum DebuggerPath {
517    /// Use a debugger at this path
518    Path(PathBuf),
519    /// Try to automatically discover a version of a debugger from the environment
520    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}