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
33use build_helper::exit;
34pub use config::*;
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::LldMode;
41pub use toml::target::Target;
42#[cfg(feature = "tracing")]
43use tracing::{instrument, span};
44
45use crate::Display;
46use crate::str::FromStr;
47
48// We are using a decl macro instead of a derive proc macro here to reduce the compile time of bootstrap.
49#[macro_export]
50macro_rules! define_config {
51    ($(#[$attr:meta])* struct $name:ident {
52        $($field:ident: Option<$field_ty:ty> = $field_key:literal,)*
53    }) => {
54        $(#[$attr])*
55        pub struct $name {
56            $(pub $field: Option<$field_ty>,)*
57        }
58
59        impl Merge for $name {
60            fn merge(
61                &mut self,
62                _parent_config_path: Option<PathBuf>,
63                _included_extensions: &mut HashSet<PathBuf>,
64                other: Self,
65                replace: ReplaceOpt
66            ) {
67                $(
68                    match replace {
69                        ReplaceOpt::IgnoreDuplicate => {
70                            if self.$field.is_none() {
71                                self.$field = other.$field;
72                            }
73                        },
74                        ReplaceOpt::Override => {
75                            if other.$field.is_some() {
76                                self.$field = other.$field;
77                            }
78                        }
79                        ReplaceOpt::ErrorOnDuplicate => {
80                            if other.$field.is_some() {
81                                if self.$field.is_some() {
82                                    if cfg!(test) {
83                                        panic!("overriding existing option")
84                                    } else {
85                                        eprintln!("overriding existing option: `{}`", stringify!($field));
86                                        exit!(2);
87                                    }
88                                } else {
89                                    self.$field = other.$field;
90                                }
91                            }
92                        }
93                    }
94                )*
95            }
96        }
97
98        // The following is a trimmed version of what serde_derive generates. All parts not relevant
99        // for toml deserialization have been removed. This reduces the binary size and improves
100        // compile time of bootstrap.
101        impl<'de> Deserialize<'de> for $name {
102            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103            where
104                D: Deserializer<'de>,
105            {
106                struct Field;
107                impl<'de> serde::de::Visitor<'de> for Field {
108                    type Value = $name;
109                    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110                        f.write_str(concat!("struct ", stringify!($name)))
111                    }
112
113                    #[inline]
114                    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
115                    where
116                        A: serde::de::MapAccess<'de>,
117                    {
118                        $(let mut $field: Option<$field_ty> = None;)*
119                        while let Some(key) =
120                            match serde::de::MapAccess::next_key::<String>(&mut map) {
121                                Ok(val) => val,
122                                Err(err) => {
123                                    return Err(err);
124                                }
125                            }
126                        {
127                            match &*key {
128                                $($field_key => {
129                                    if $field.is_some() {
130                                        return Err(<A::Error as serde::de::Error>::duplicate_field(
131                                            $field_key,
132                                        ));
133                                    }
134                                    $field = match serde::de::MapAccess::next_value::<$field_ty>(
135                                        &mut map,
136                                    ) {
137                                        Ok(val) => Some(val),
138                                        Err(err) => {
139                                            return Err(err);
140                                        }
141                                    };
142                                })*
143                                key => {
144                                    return Err(serde::de::Error::unknown_field(key, FIELDS));
145                                }
146                            }
147                        }
148                        Ok($name { $($field),* })
149                    }
150                }
151                const FIELDS: &'static [&'static str] = &[
152                    $($field_key,)*
153                ];
154                Deserializer::deserialize_struct(
155                    deserializer,
156                    stringify!($name),
157                    FIELDS,
158                    Field,
159                )
160            }
161        }
162    }
163}
164
165#[macro_export]
166macro_rules! check_ci_llvm {
167    ($name:expr) => {
168        assert!(
169            $name.is_none(),
170            "setting {} is incompatible with download-ci-llvm.",
171            stringify!($name).replace("_", "-")
172        );
173    };
174}
175
176pub(crate) trait Merge {
177    fn merge(
178        &mut self,
179        parent_config_path: Option<PathBuf>,
180        included_extensions: &mut HashSet<PathBuf>,
181        other: Self,
182        replace: ReplaceOpt,
183    );
184}
185
186impl<T> Merge for Option<T> {
187    fn merge(
188        &mut self,
189        _parent_config_path: Option<PathBuf>,
190        _included_extensions: &mut HashSet<PathBuf>,
191        other: Self,
192        replace: ReplaceOpt,
193    ) {
194        match replace {
195            ReplaceOpt::IgnoreDuplicate => {
196                if self.is_none() {
197                    *self = other;
198                }
199            }
200            ReplaceOpt::Override => {
201                if other.is_some() {
202                    *self = other;
203                }
204            }
205            ReplaceOpt::ErrorOnDuplicate => {
206                if other.is_some() {
207                    if self.is_some() {
208                        if cfg!(test) {
209                            panic!("overriding existing option")
210                        } else {
211                            eprintln!("overriding existing option");
212                            exit!(2);
213                        }
214                    } else {
215                        *self = other;
216                    }
217                }
218            }
219        }
220    }
221}
222
223#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
224pub enum DebuginfoLevel {
225    #[default]
226    None,
227    LineDirectivesOnly,
228    LineTablesOnly,
229    Limited,
230    Full,
231}
232
233// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only
234// deserializes i64, and derive() only generates visit_u64
235impl<'de> Deserialize<'de> for DebuginfoLevel {
236    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237    where
238        D: Deserializer<'de>,
239    {
240        use serde::de::Error;
241
242        Ok(match Deserialize::deserialize(deserializer)? {
243            StringOrInt::String(s) if s == "none" => DebuginfoLevel::None,
244            StringOrInt::Int(0) => DebuginfoLevel::None,
245            StringOrInt::String(s) if s == "line-directives-only" => {
246                DebuginfoLevel::LineDirectivesOnly
247            }
248            StringOrInt::String(s) if s == "line-tables-only" => DebuginfoLevel::LineTablesOnly,
249            StringOrInt::String(s) if s == "limited" => DebuginfoLevel::Limited,
250            StringOrInt::Int(1) => DebuginfoLevel::Limited,
251            StringOrInt::String(s) if s == "full" => DebuginfoLevel::Full,
252            StringOrInt::Int(2) => DebuginfoLevel::Full,
253            StringOrInt::Int(n) => {
254                let other = serde::de::Unexpected::Signed(n);
255                return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2"));
256            }
257            StringOrInt::String(s) => {
258                let other = serde::de::Unexpected::Str(&s);
259                return Err(D::Error::invalid_value(
260                    other,
261                    &"expected none, line-tables-only, limited, or full",
262                ));
263            }
264        })
265    }
266}
267
268/// Suitable for passing to `-C debuginfo`
269impl Display for DebuginfoLevel {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        use DebuginfoLevel::*;
272        f.write_str(match self {
273            None => "0",
274            LineDirectivesOnly => "line-directives-only",
275            LineTablesOnly => "line-tables-only",
276            Limited => "1",
277            Full => "2",
278        })
279    }
280}
281
282#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
283#[serde(untagged)]
284pub enum StringOrBool {
285    String(String),
286    Bool(bool),
287}
288
289impl Default for StringOrBool {
290    fn default() -> StringOrBool {
291        StringOrBool::Bool(false)
292    }
293}
294
295impl StringOrBool {
296    pub fn is_string_or_true(&self) -> bool {
297        matches!(self, Self::String(_) | Self::Bool(true))
298    }
299}
300
301#[derive(Deserialize)]
302#[serde(untagged)]
303pub enum StringOrInt {
304    String(String),
305    Int(i64),
306}
307
308#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
309pub enum LlvmLibunwind {
310    #[default]
311    No,
312    InTree,
313    System,
314}
315
316impl FromStr for LlvmLibunwind {
317    type Err = String;
318
319    fn from_str(value: &str) -> Result<Self, Self::Err> {
320        match value {
321            "no" => Ok(Self::No),
322            "in-tree" => Ok(Self::InTree),
323            "system" => Ok(Self::System),
324            invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")),
325        }
326    }
327}
328
329#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330pub enum SplitDebuginfo {
331    Packed,
332    Unpacked,
333    #[default]
334    Off,
335}
336
337impl std::str::FromStr for SplitDebuginfo {
338    type Err = ();
339
340    fn from_str(s: &str) -> Result<Self, Self::Err> {
341        match s {
342            "packed" => Ok(SplitDebuginfo::Packed),
343            "unpacked" => Ok(SplitDebuginfo::Unpacked),
344            "off" => Ok(SplitDebuginfo::Off),
345            _ => Err(()),
346        }
347    }
348}
349
350/// Describes how to handle conflicts in merging two `TomlConfig`
351#[derive(Copy, Clone, Debug)]
352pub enum ReplaceOpt {
353    /// Silently ignore a duplicated value
354    IgnoreDuplicate,
355    /// Override the current value, even if it's `Some`
356    Override,
357    /// Exit with an error on duplicate values
358    ErrorOnDuplicate,
359}
360
361#[derive(Clone, Default)]
362pub enum DryRun {
363    /// This isn't a dry run.
364    #[default]
365    Disabled,
366    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
367    SelfCheck,
368    /// This is a dry run enabled by the `--dry-run` flag.
369    UserSelected,
370}
371
372/// LTO mode used for compiling rustc itself.
373#[derive(Default, Clone, PartialEq, Debug)]
374pub enum RustcLto {
375    Off,
376    #[default]
377    ThinLocal,
378    Thin,
379    Fat,
380}
381
382impl std::str::FromStr for RustcLto {
383    type Err = String;
384
385    fn from_str(s: &str) -> Result<Self, Self::Err> {
386        match s {
387            "thin-local" => Ok(RustcLto::ThinLocal),
388            "thin" => Ok(RustcLto::Thin),
389            "fat" => Ok(RustcLto::Fat),
390            "off" => Ok(RustcLto::Off),
391            _ => Err(format!("Invalid value for rustc LTO: {s}")),
392        }
393    }
394}
395
396/// Determines how will GCC be provided.
397#[derive(Default, Clone)]
398pub enum GccCiMode {
399    /// Build GCC from the local `src/gcc` submodule.
400    #[default]
401    BuildLocally,
402    /// Try to download GCC from CI.
403    /// If it is not available on CI, it will be built locally instead.
404    DownloadFromCi,
405}
406
407pub fn set<T>(field: &mut T, val: Option<T>) {
408    if let Some(v) = val {
409        *field = v;
410    }
411}
412
413pub fn threads_from_config(v: u32) -> u32 {
414    match v {
415        0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
416        n => n,
417    }
418}