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::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// We are using a decl macro instead of a derive proc macro here to reduce the compile time of bootstrap.
47#[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        // The following is a trimmed version of what serde_derive generates. All parts not relevant
103        // for toml deserialization have been removed. This reduces the binary size and improves
104        // compile time of bootstrap.
105        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    // Only build native rust intrinsic compiler functions.
231    BuildRustOnly,
232    // Some intrinsic functions have a C implementation provided by LLVM's
233    // compiler-rt builtins library. Build them from the LLVM source included
234    // with Rust.
235    BuildLLVMFuncs,
236    // Similar to BuildLLVMFuncs, but specify a path to an existing library
237    // containing LLVM's compiler-rt builtins instead of compiling them.
238    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
290// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only
291// deserializes i64, and derive() only generates visit_u64
292impl<'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
325/// Suitable for passing to `-C debuginfo`
326impl 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/// Describes how to handle conflicts in merging two `TomlConfig`
455#[derive(Copy, Clone, Debug)]
456pub enum ReplaceOpt {
457    /// Silently ignore a duplicated value
458    IgnoreDuplicate,
459    /// Override the current value, even if it's `Some`
460    Override,
461    /// Exit with an error on duplicate values
462    ErrorOnDuplicate,
463}
464
465#[derive(Clone, Default)]
466pub enum DryRun {
467    /// This isn't a dry run.
468    #[default]
469    Disabled,
470    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
471    SelfCheck,
472    /// This is a dry run enabled by the `--dry-run` flag.
473    UserSelected,
474}
475
476/// LTO mode used for compiling rustc itself.
477#[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/// Determines how will GCC be provided.
501#[derive(Default, Debug, Clone, PartialEq)]
502pub enum GccCiMode {
503    /// Build GCC from the local `src/gcc` submodule.
504    BuildLocally,
505    /// Try to download GCC from CI.
506    /// If it is not available on CI, it will be built locally instead.
507    #[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}