Skip to main content

bootstrap/core/config/
mod.rs

1//! Entry point for the `config` module.
2//!
3//! Additionally, this module defines common types, enums, and helper functions used across
4//! various TOML configuration sections in `bootstrap.toml`.
5//!
6//! It provides shared definitions for:
7//! - Data types deserialized from TOML.
8//! - Utility enums for specific configuration options.
9//! - Helper functions for managing configuration values.
10
11#[expect(clippy::module_inception)]
12mod config;
13pub mod flags;
14pub(crate) mod macros;
15pub mod target_selection;
16#[cfg(test)]
17mod tests;
18pub mod toml;
19
20use std::collections::HashSet;
21use std::fmt::Display;
22use std::path::PathBuf;
23use std::str::FromStr;
24
25pub use config::*;
26use serde::de::Unexpected;
27use serde::{Deserialize, Deserializer};
28use serde_derive::Deserialize;
29pub use target_selection::TargetSelection;
30pub use toml::BUILDER_CONFIG_FILENAME;
31pub use toml::change_id::ChangeId;
32pub use toml::rust::BootstrapOverrideLld;
33pub use toml::target::Target;
34
35use crate::utils::helpers;
36
37pub(crate) trait Merge {
38    fn merge(
39        &mut self,
40        parent_config_path: Option<PathBuf>,
41        included_extensions: &mut HashSet<PathBuf>,
42        other: Self,
43        replace: ReplaceOpt,
44    );
45}
46
47impl<T> Merge for Option<T> {
48    fn merge(
49        &mut self,
50        _parent_config_path: Option<PathBuf>,
51        _included_extensions: &mut HashSet<PathBuf>,
52        other: Self,
53        replace: ReplaceOpt,
54    ) {
55        match replace {
56            ReplaceOpt::IgnoreDuplicate => {
57                if self.is_none() {
58                    *self = other;
59                }
60            }
61            ReplaceOpt::Override => {
62                if other.is_some() {
63                    *self = other;
64                }
65            }
66            ReplaceOpt::ErrorOnDuplicate => {
67                if other.is_some() {
68                    if self.is_some() {
69                        if cfg!(test) {
70                            panic!("overriding existing option")
71                        } else {
72                            eprintln!("overriding existing option");
73                            helpers::exit_process(2);
74                        }
75                    } else {
76                        *self = other;
77                    }
78                }
79            }
80        }
81    }
82}
83
84#[derive(Clone, Debug, Default, Eq, PartialEq)]
85pub enum CompilerBuiltins {
86    #[default]
87    // Only build native rust intrinsic compiler functions.
88    BuildRustOnly,
89    // Some intrinsic functions have a C implementation provided by LLVM's
90    // compiler-rt builtins library. Build them from the LLVM source included
91    // with Rust.
92    BuildLLVMFuncs,
93    // Similar to BuildLLVMFuncs, but specify a path to an existing library
94    // containing LLVM's compiler-rt builtins instead of compiling them.
95    LinkLLVMBuiltinsLib(String),
96}
97
98impl<'de> Deserialize<'de> for CompilerBuiltins {
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: Deserializer<'de>,
102    {
103        Ok(match Deserialize::deserialize(deserializer)? {
104            StringOrBool::Bool(false) => Self::BuildRustOnly,
105            StringOrBool::Bool(true) => Self::BuildLLVMFuncs,
106            StringOrBool::String(path) => Self::LinkLLVMBuiltinsLib(path),
107        })
108    }
109}
110
111#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub enum Allocator {
113    System,
114    Jemalloc,
115}
116
117impl Allocator {
118    pub fn feature_name(self) -> Option<&'static str> {
119        match self {
120            Allocator::System => None,
121            Allocator::Jemalloc => Some("jemalloc"),
122        }
123    }
124}
125
126impl<'de> Deserialize<'de> for Allocator {
127    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128    where
129        D: Deserializer<'de>,
130    {
131        let name = String::deserialize(deserializer)?;
132        match name.as_str() {
133            "system" => Ok(Self::System),
134            "jemalloc" => Ok(Self::Jemalloc),
135            other => Err(serde::de::Error::unknown_variant(other, &["system", "jemalloc"])),
136        }
137    }
138}
139
140#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
141pub enum DebuginfoLevel {
142    #[default]
143    None,
144    LineDirectivesOnly,
145    LineTablesOnly,
146    Limited,
147    Full,
148}
149
150// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only
151// deserializes i64, and derive() only generates visit_u64
152impl<'de> Deserialize<'de> for DebuginfoLevel {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        use serde::de::Error;
158
159        Ok(match Deserialize::deserialize(deserializer)? {
160            StringOrInt::String(s) if s == "none" => DebuginfoLevel::None,
161            StringOrInt::Int(0) => DebuginfoLevel::None,
162            StringOrInt::String(s) if s == "line-directives-only" => {
163                DebuginfoLevel::LineDirectivesOnly
164            }
165            StringOrInt::String(s) if s == "line-tables-only" => DebuginfoLevel::LineTablesOnly,
166            StringOrInt::String(s) if s == "limited" => DebuginfoLevel::Limited,
167            StringOrInt::Int(1) => DebuginfoLevel::Limited,
168            StringOrInt::String(s) if s == "full" => DebuginfoLevel::Full,
169            StringOrInt::Int(2) => DebuginfoLevel::Full,
170            StringOrInt::Int(n) => {
171                let other = serde::de::Unexpected::Signed(n);
172                return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2"));
173            }
174            StringOrInt::String(s) => {
175                let other = serde::de::Unexpected::Str(&s);
176                return Err(D::Error::invalid_value(
177                    other,
178                    &"expected none, line-tables-only, limited, or full",
179                ));
180            }
181        })
182    }
183}
184
185/// Suitable for passing to `-C debuginfo`
186impl Display for DebuginfoLevel {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        use DebuginfoLevel::*;
189        f.write_str(match self {
190            None => "0",
191            LineDirectivesOnly => "line-directives-only",
192            LineTablesOnly => "line-tables-only",
193            Limited => "1",
194            Full => "2",
195        })
196    }
197}
198
199#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
200#[serde(untagged)]
201pub enum StringOrBool {
202    String(String),
203    Bool(bool),
204}
205
206impl Default for StringOrBool {
207    fn default() -> StringOrBool {
208        StringOrBool::Bool(false)
209    }
210}
211
212impl StringOrBool {
213    pub fn is_string_or_true(&self) -> bool {
214        matches!(self, Self::String(_) | Self::Bool(true))
215    }
216}
217
218#[derive(Deserialize)]
219#[serde(untagged)]
220pub enum StringOrInt {
221    String(String),
222    Int(i64),
223}
224
225#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
226pub enum LlvmLibunwind {
227    #[default]
228    No,
229    InTree,
230    System,
231}
232
233impl FromStr for LlvmLibunwind {
234    type Err = String;
235
236    fn from_str(value: &str) -> Result<Self, Self::Err> {
237        match value {
238            "no" => Ok(Self::No),
239            "in-tree" => Ok(Self::InTree),
240            "system" => Ok(Self::System),
241            invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")),
242        }
243    }
244}
245
246#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
247pub enum SplitDebuginfo {
248    Packed,
249    Unpacked,
250    #[default]
251    Off,
252}
253
254impl std::str::FromStr for SplitDebuginfo {
255    type Err = ();
256
257    fn from_str(s: &str) -> Result<Self, Self::Err> {
258        match s {
259            "packed" => Ok(SplitDebuginfo::Packed),
260            "unpacked" => Ok(SplitDebuginfo::Unpacked),
261            "off" => Ok(SplitDebuginfo::Off),
262            _ => Err(()),
263        }
264    }
265}
266
267#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
268pub enum CompressDebuginfo {
269    Zlib,
270    #[default]
271    Off,
272}
273
274impl CompressDebuginfo {
275    fn default_on() -> Self {
276        Self::Zlib
277    }
278}
279
280impl std::str::FromStr for CompressDebuginfo {
281    type Err = ();
282
283    fn from_str(s: &str) -> Result<Self, Self::Err> {
284        match s {
285            "zlib" => Ok(CompressDebuginfo::Zlib),
286            "off" => Ok(CompressDebuginfo::Off),
287            _ => Err(()),
288        }
289    }
290}
291
292impl<'de> Deserialize<'de> for CompressDebuginfo {
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            StringOrBool::Bool(value) => {
301                if value {
302                    CompressDebuginfo::default_on()
303                } else {
304                    CompressDebuginfo::Off
305                }
306            }
307            StringOrBool::String(value) => CompressDebuginfo::from_str(&value).map_err(|_| {
308                D::Error::invalid_value(Unexpected::Str(&value), &"`zlib` or `off`")
309            })?,
310        })
311    }
312}
313
314/// Describes how to handle conflicts in merging two `TomlConfig`
315#[derive(Copy, Clone, Debug)]
316pub enum ReplaceOpt {
317    /// Silently ignore a duplicated value
318    IgnoreDuplicate,
319    /// Override the current value, even if it's `Some`
320    Override,
321    /// Exit with an error on duplicate values
322    ErrorOnDuplicate,
323}
324
325#[derive(Clone, Default)]
326pub enum DryRun {
327    /// This isn't a dry run.
328    #[default]
329    Disabled,
330    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
331    SelfCheck,
332    /// This is a dry run enabled by the `--dry-run` flag.
333    UserSelected,
334}
335
336/// LTO mode used for compiling rustc itself.
337#[derive(Default, Clone, PartialEq, Debug)]
338pub enum RustcLto {
339    Off,
340    #[default]
341    ThinLocal,
342    Thin,
343    Fat,
344}
345
346impl std::str::FromStr for RustcLto {
347    type Err = String;
348
349    fn from_str(s: &str) -> Result<Self, Self::Err> {
350        match s {
351            "thin-local" => Ok(RustcLto::ThinLocal),
352            "thin" => Ok(RustcLto::Thin),
353            "fat" => Ok(RustcLto::Fat),
354            "off" => Ok(RustcLto::Off),
355            _ => Err(format!("Invalid value for rustc LTO: {s}")),
356        }
357    }
358}
359
360/// Determines how will GCC be provided.
361#[derive(Default, Debug, Clone, PartialEq)]
362pub enum GccCiMode {
363    /// Build GCC from the local `src/gcc` submodule.
364    BuildLocally,
365    /// Try to download GCC from CI.
366    /// If it is not available on CI, it will be built locally instead.
367    #[default]
368    DownloadFromCi,
369}
370
371#[derive(Clone, Debug, PartialEq)]
372pub enum DebuggerPath {
373    /// Use a debugger at this path
374    Path(PathBuf),
375    /// Try to automatically discover a version of a debugger from the environment
376    Discover,
377}
378
379impl<'d> Deserialize<'d> for DebuggerPath {
380    fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'d>>::Error>
381    where
382        D: serde::Deserializer<'d>,
383    {
384        let value = String::deserialize(deserializer)?;
385        match value.as_str() {
386            "discover" => Ok(Self::Discover),
387            path => Ok(Self::Path(PathBuf::from(path))),
388        }
389    }
390}
391
392pub fn threads_from_config(v: u32) -> u32 {
393    match v {
394        0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
395        n => n,
396    }
397}