Skip to main content

bootstrap/core/config/toml/
mod.rs

1//! This module defines the structures that directly mirror the `bootstrap.toml`
2//! file's format. These types are used for `serde` deserialization.
3//!
4//! Crucially, this module also houses the core logic for loading, parsing, and merging
5//! these raw TOML configurations from various sources (the main `bootstrap.toml`,
6//! included files, profile defaults, and command-line overrides). This processed
7//! TOML data then serves as an intermediate representation, which is further
8//! transformed and applied to the final `Config` struct.
9
10use serde::Deserialize;
11use serde_derive::Deserialize;
12pub mod build;
13pub mod change_id;
14pub mod dist;
15pub mod gcc;
16pub mod install;
17pub mod llvm;
18pub mod pgo;
19pub mod rust;
20pub mod target;
21
22use std::collections::{HashMap, HashSet};
23use std::fs;
24use std::path::{Path, PathBuf};
25
26use build::Build;
27use change_id::{ChangeId, ChangeIdWrapper};
28use dist::Dist;
29use gcc::Gcc;
30use install::Install;
31use llvm::Llvm;
32use rust::Rust;
33use target::TomlTarget;
34
35use crate::core::config::toml::pgo::Pgo;
36use crate::core::config::{Config, Merge, ReplaceOpt};
37use crate::exit;
38use crate::utils::change_tracker::{find_recent_config_change_ids, human_readable_changes};
39use crate::utils::helpers::t;
40
41/// Structure of the `bootstrap.toml` file that configuration is read from.
42///
43/// This structure uses `Decodable` to automatically decode a TOML configuration
44/// file into this format, and then this is traversed and written into the above
45/// `Config` structure.
46#[derive(Deserialize, Default)]
47#[serde(deny_unknown_fields, rename_all = "kebab-case")]
48pub(crate) struct TomlConfig {
49    #[serde(flatten)]
50    pub(crate) change_id: ChangeIdWrapper,
51    pub(super) build: Option<Build>,
52    pub(super) install: Option<Install>,
53    pub(super) llvm: Option<Llvm>,
54    pub(super) gcc: Option<Gcc>,
55    pub(super) rust: Option<Rust>,
56    pub(super) target: Option<HashMap<String, TomlTarget>>,
57    pub(super) dist: Option<Dist>,
58    pub(super) pgo: Option<Pgo>,
59    pub(super) profile: Option<String>,
60    pub(super) include: Option<Vec<PathBuf>>,
61}
62
63impl Merge for TomlConfig {
64    fn merge(
65        &mut self,
66        parent_config_path: Option<PathBuf>,
67        included_extensions: &mut HashSet<PathBuf>,
68        TomlConfig {
69            build,
70            install,
71            llvm,
72            gcc,
73            rust,
74            dist,
75            target,
76            pgo,
77            profile,
78            change_id,
79            include,
80        }: Self,
81        replace: ReplaceOpt,
82    ) {
83        fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
84            if let Some(new) = y {
85                if let Some(original) = x {
86                    original.merge(None, &mut Default::default(), new, replace);
87                } else {
88                    *x = Some(new);
89                }
90            }
91        }
92
93        self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace);
94        self.profile.merge(None, &mut Default::default(), profile, replace);
95
96        do_merge(&mut self.build, build, replace);
97        do_merge(&mut self.install, install, replace);
98        do_merge(&mut self.llvm, llvm, replace);
99        do_merge(&mut self.gcc, gcc, replace);
100        do_merge(&mut self.rust, rust, replace);
101        do_merge(&mut self.dist, dist, replace);
102        do_merge(&mut self.pgo, pgo, replace);
103
104        match (self.target.as_mut(), target) {
105            (_, None) => {}
106            (None, Some(target)) => self.target = Some(target),
107            (Some(original_target), Some(new_target)) => {
108                for (triple, new) in new_target {
109                    if let Some(original) = original_target.get_mut(&triple) {
110                        original.merge(None, &mut Default::default(), new, replace);
111                    } else {
112                        original_target.insert(triple, new);
113                    }
114                }
115            }
116        }
117
118        let parent_dir = parent_config_path
119            .as_ref()
120            .and_then(|p| p.parent().map(ToOwned::to_owned))
121            .unwrap_or_default();
122
123        // `include` handled later since we ignore duplicates using `ReplaceOpt::IgnoreDuplicate` to
124        // keep the upper-level configuration to take precedence.
125        for include_path in include.clone().unwrap_or_default().iter().rev() {
126            let include_path = parent_dir.join(include_path);
127            let include_path = include_path.canonicalize().unwrap_or_else(|e| {
128                eprintln!("ERROR: Failed to canonicalize '{}' path: {e}", include_path.display());
129                exit!(2);
130            });
131
132            let included_toml = Config::get_toml_inner(&include_path).unwrap_or_else(|e| {
133                eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
134                exit!(2);
135            });
136
137            assert!(
138                included_extensions.insert(include_path.clone()),
139                "Cyclic inclusion detected: '{}' is being included again before its previous inclusion was fully processed.",
140                include_path.display()
141            );
142
143            self.merge(
144                Some(include_path.clone()),
145                included_extensions,
146                included_toml,
147                // Ensures that parent configuration always takes precedence
148                // over child configurations.
149                ReplaceOpt::IgnoreDuplicate,
150            );
151
152            included_extensions.remove(&include_path);
153        }
154    }
155}
156
157/// This file is embedded in the overlay directory of the tarball sources. It is
158/// useful in scenarios where developers want to see how the tarball sources were
159/// generated.
160///
161/// We also use this file to compare the host's bootstrap.toml against the CI rustc builder
162/// configuration to detect any incompatible options.
163pub const BUILDER_CONFIG_FILENAME: &str = "builder-config";
164
165impl Config {
166    pub(crate) fn get_builder_toml(&self, build_name: &str) -> Result<TomlConfig, toml::de::Error> {
167        if self.dry_run() {
168            return Ok(TomlConfig::default());
169        }
170
171        let builder_config_path =
172            self.out.join(self.host_target.triple).join(build_name).join(BUILDER_CONFIG_FILENAME);
173        Self::get_toml(&builder_config_path)
174    }
175
176    pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
177        Self::get_toml_inner(file)
178    }
179
180    pub(crate) fn get_toml_inner(file: &Path) -> Result<TomlConfig, toml::de::Error> {
181        let contents =
182            t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
183        // Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
184        // TomlConfig and sub types to be monomorphized 5x by toml.
185        toml::from_str(&contents)
186            .and_then(|table: toml::Value| TomlConfig::deserialize(table))
187            .inspect_err(|_| {
188                if let Ok(ChangeIdWrapper { inner: Some(ChangeId::Id(id)) }) =
189                    toml::from_str::<toml::Value>(&contents)
190                        .and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
191                {
192                    let changes = find_recent_config_change_ids(id);
193                    if !changes.is_empty() {
194                        println!(
195                            "WARNING: There have been changes to x.py since you last updated:\n{}",
196                            human_readable_changes(changes)
197                        );
198                    }
199                }
200            })
201    }
202}