Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d00435f

Browse files
committedJun 6, 2025·
Auto merge of #141272 - Shourya742:2025-05-18-modularize-config-module, r=Kobzol
modularize the config module bootstrap Currently, our `config` module is quite large over 3,000 lines, and handles a wide range of responsibilities. This PR aims to break it down into smaller, more focused submodules to improve readability and maintainability: * **`toml`**: Introduces a dedicated `toml` submodule within the `config` module. Its sole purpose is to define configuration-related structs along with their corresponding deserialization logic. It also contains the `parse_inner` method, which serves as the central function for extracting relevant information from the TOML structs and constructing the final configuration. * **`rust`, `dist`, `install`, `llvm`, `build`, `gcc`, and others**: Each of these modules contains TOML subsections specific to their domain, along with the logic necessary to convert them into parts of the final configuration struct. * **`config/mod.rs`**: Contains shared types and enums used across multiple TOML subsections. * **`config/config.rs`**: Houses the logic that integrates all the TOML subsections into the complete configuration struct. r? `@kobzol`
2 parents cf42371 + 3667dbd commit d00435f

File tree

15 files changed

+2238
-1996
lines changed

15 files changed

+2238
-1996
lines changed
 

‎src/bootstrap/src/core/config/config.rs

Lines changed: 132 additions & 1992 deletions
Large diffs are not rendered by default.

‎src/bootstrap/src/core/config/flags.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use tracing::instrument;
1212
use crate::core::build_steps::perf::PerfArgs;
1313
use crate::core::build_steps::setup::Profile;
1414
use crate::core::builder::{Builder, Kind};
15-
use crate::core::config::{Config, TargetSelectionList, target_selection_list};
15+
use crate::core::config::Config;
16+
use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
1617
use crate::{Build, DocTests};
1718

1819
#[derive(Copy, Clone, Default, Debug, ValueEnum)]

‎src/bootstrap/src/core/config/mod.rs

Lines changed: 411 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use std::fmt;
2+
3+
use crate::core::config::SplitDebuginfo;
4+
use crate::utils::cache::{INTERNER, Interned};
5+
use crate::{Path, env};
6+
7+
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
8+
// N.B.: This type is used everywhere, and the entire codebase relies on it being Copy.
9+
// Making !Copy is highly nontrivial!
10+
pub struct TargetSelection {
11+
pub triple: Interned<String>,
12+
pub file: Option<Interned<String>>,
13+
pub synthetic: bool,
14+
}
15+
16+
/// Newtype over `Vec<TargetSelection>` so we can implement custom parsing logic
17+
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
18+
pub struct TargetSelectionList(pub Vec<TargetSelection>);
19+
20+
pub fn target_selection_list(s: &str) -> Result<TargetSelectionList, String> {
21+
Ok(TargetSelectionList(
22+
s.split(',').filter(|s| !s.is_empty()).map(TargetSelection::from_user).collect(),
23+
))
24+
}
25+
26+
impl TargetSelection {
27+
pub fn from_user(selection: &str) -> Self {
28+
let path = Path::new(selection);
29+
30+
let (triple, file) = if path.exists() {
31+
let triple = path
32+
.file_stem()
33+
.expect("Target specification file has no file stem")
34+
.to_str()
35+
.expect("Target specification file stem is not UTF-8");
36+
37+
(triple, Some(selection))
38+
} else {
39+
(selection, None)
40+
};
41+
42+
let triple = INTERNER.intern_str(triple);
43+
let file = file.map(|f| INTERNER.intern_str(f));
44+
45+
Self { triple, file, synthetic: false }
46+
}
47+
48+
pub fn create_synthetic(triple: &str, file: &str) -> Self {
49+
Self {
50+
triple: INTERNER.intern_str(triple),
51+
file: Some(INTERNER.intern_str(file)),
52+
synthetic: true,
53+
}
54+
}
55+
56+
pub fn rustc_target_arg(&self) -> &str {
57+
self.file.as_ref().unwrap_or(&self.triple)
58+
}
59+
60+
pub fn contains(&self, needle: &str) -> bool {
61+
self.triple.contains(needle)
62+
}
63+
64+
pub fn starts_with(&self, needle: &str) -> bool {
65+
self.triple.starts_with(needle)
66+
}
67+
68+
pub fn ends_with(&self, needle: &str) -> bool {
69+
self.triple.ends_with(needle)
70+
}
71+
72+
// See src/bootstrap/synthetic_targets.rs
73+
pub fn is_synthetic(&self) -> bool {
74+
self.synthetic
75+
}
76+
77+
pub fn is_msvc(&self) -> bool {
78+
self.contains("msvc")
79+
}
80+
81+
pub fn is_windows(&self) -> bool {
82+
self.contains("windows")
83+
}
84+
85+
pub fn is_windows_gnu(&self) -> bool {
86+
self.ends_with("windows-gnu")
87+
}
88+
89+
pub fn is_cygwin(&self) -> bool {
90+
self.is_windows() &&
91+
// ref. https://cygwin.com/pipermail/cygwin/2022-February/250802.html
92+
env::var("OSTYPE").is_ok_and(|v| v.to_lowercase().contains("cygwin"))
93+
}
94+
95+
pub fn needs_crt_begin_end(&self) -> bool {
96+
self.contains("musl") && !self.contains("unikraft")
97+
}
98+
99+
/// Path to the file defining the custom target, if any.
100+
pub fn filepath(&self) -> Option<&Path> {
101+
self.file.as_ref().map(Path::new)
102+
}
103+
}
104+
105+
impl fmt::Display for TargetSelection {
106+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107+
write!(f, "{}", self.triple)?;
108+
if let Some(file) = self.file {
109+
write!(f, "({file})")?;
110+
}
111+
Ok(())
112+
}
113+
}
114+
115+
impl fmt::Debug for TargetSelection {
116+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117+
write!(f, "{self}")
118+
}
119+
}
120+
121+
impl PartialEq<&str> for TargetSelection {
122+
fn eq(&self, other: &&str) -> bool {
123+
self.triple == *other
124+
}
125+
}
126+
127+
// Targets are often used as directory names throughout bootstrap.
128+
// This impl makes it more ergonomics to use them as such.
129+
impl AsRef<Path> for TargetSelection {
130+
fn as_ref(&self) -> &Path {
131+
self.triple.as_ref()
132+
}
133+
}
134+
135+
impl SplitDebuginfo {
136+
/// Returns the default `-Csplit-debuginfo` value for the current target. See the comment for
137+
/// `rust.split-debuginfo` in `bootstrap.example.toml`.
138+
pub fn default_for_platform(target: TargetSelection) -> Self {
139+
if target.contains("apple") {
140+
SplitDebuginfo::Unpacked
141+
} else if target.is_windows() {
142+
SplitDebuginfo::Packed
143+
} else {
144+
SplitDebuginfo::Off
145+
}
146+
}
147+
}

‎src/bootstrap/src/core/config/tests.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use clap::CommandFactory;
1010
use serde::Deserialize;
1111

1212
use super::flags::Flags;
13-
use super::{ChangeIdWrapper, Config, RUSTC_IF_UNCHANGED_ALLOWED_PATHS};
13+
use super::toml::change_id::ChangeIdWrapper;
14+
use super::{Config, RUSTC_IF_UNCHANGED_ALLOWED_PATHS};
1415
use crate::ChangeId;
1516
use crate::core::build_steps::clippy::{LintConfig, get_clippy_rules_in_order};
1617
use crate::core::build_steps::llvm;
1718
use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS;
18-
use crate::core::config::{LldMode, Target, TargetSelection, TomlConfig};
19+
use crate::core::config::toml::TomlConfig;
20+
use crate::core::config::{LldMode, Target, TargetSelection};
1921
use crate::utils::tests::git::git_test;
2022

2123
pub(crate) fn parse(config: &str) -> Config {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! This module defines the `Build` struct, which represents the `[build]` table
2+
//! in the `bootstrap.toml` configuration file.
3+
//!
4+
//! The `[build]` table contains global options that influence the overall build process,
5+
//! such as default host and target triples, paths to tools, build directories, and
6+
//! various feature flags. These options apply across different stages and components
7+
//! unless specifically overridden by other configuration sections or command-line flags.
8+
9+
use serde::{Deserialize, Deserializer};
10+
11+
use crate::core::config::toml::ReplaceOpt;
12+
use crate::core::config::{Merge, StringOrBool};
13+
use crate::{HashSet, PathBuf, define_config, exit};
14+
15+
define_config! {
16+
/// TOML representation of various global build decisions.
17+
#[derive(Default)]
18+
struct Build {
19+
build: Option<String> = "build",
20+
description: Option<String> = "description",
21+
host: Option<Vec<String>> = "host",
22+
target: Option<Vec<String>> = "target",
23+
build_dir: Option<String> = "build-dir",
24+
cargo: Option<PathBuf> = "cargo",
25+
rustc: Option<PathBuf> = "rustc",
26+
rustfmt: Option<PathBuf> = "rustfmt",
27+
cargo_clippy: Option<PathBuf> = "cargo-clippy",
28+
docs: Option<bool> = "docs",
29+
compiler_docs: Option<bool> = "compiler-docs",
30+
library_docs_private_items: Option<bool> = "library-docs-private-items",
31+
docs_minification: Option<bool> = "docs-minification",
32+
submodules: Option<bool> = "submodules",
33+
gdb: Option<String> = "gdb",
34+
lldb: Option<String> = "lldb",
35+
nodejs: Option<String> = "nodejs",
36+
npm: Option<String> = "npm",
37+
python: Option<String> = "python",
38+
reuse: Option<String> = "reuse",
39+
locked_deps: Option<bool> = "locked-deps",
40+
vendor: Option<bool> = "vendor",
41+
full_bootstrap: Option<bool> = "full-bootstrap",
42+
bootstrap_cache_path: Option<PathBuf> = "bootstrap-cache-path",
43+
extended: Option<bool> = "extended",
44+
tools: Option<HashSet<String>> = "tools",
45+
verbose: Option<usize> = "verbose",
46+
sanitizers: Option<bool> = "sanitizers",
47+
profiler: Option<bool> = "profiler",
48+
cargo_native_static: Option<bool> = "cargo-native-static",
49+
low_priority: Option<bool> = "low-priority",
50+
configure_args: Option<Vec<String>> = "configure-args",
51+
local_rebuild: Option<bool> = "local-rebuild",
52+
print_step_timings: Option<bool> = "print-step-timings",
53+
print_step_rusage: Option<bool> = "print-step-rusage",
54+
check_stage: Option<u32> = "check-stage",
55+
doc_stage: Option<u32> = "doc-stage",
56+
build_stage: Option<u32> = "build-stage",
57+
test_stage: Option<u32> = "test-stage",
58+
install_stage: Option<u32> = "install-stage",
59+
dist_stage: Option<u32> = "dist-stage",
60+
bench_stage: Option<u32> = "bench-stage",
61+
patch_binaries_for_nix: Option<bool> = "patch-binaries-for-nix",
62+
// NOTE: only parsed by bootstrap.py, `--feature build-metrics` enables metrics unconditionally
63+
metrics: Option<bool> = "metrics",
64+
android_ndk: Option<PathBuf> = "android-ndk",
65+
optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
66+
jobs: Option<u32> = "jobs",
67+
compiletest_diff_tool: Option<String> = "compiletest-diff-tool",
68+
compiletest_use_stage0_libtest: Option<bool> = "compiletest-use-stage0-libtest",
69+
ccache: Option<StringOrBool> = "ccache",
70+
exclude: Option<Vec<PathBuf>> = "exclude",
71+
}
72+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use serde::{Deserialize, Deserializer};
2+
use serde_derive::Deserialize;
3+
4+
/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`.
5+
#[derive(Clone, Debug, PartialEq)]
6+
pub enum ChangeId {
7+
Ignore,
8+
Id(usize),
9+
}
10+
11+
/// Since we use `#[serde(deny_unknown_fields)]` on `TomlConfig`, we need a wrapper type
12+
/// for the "change-id" field to parse it even if other fields are invalid. This ensures
13+
/// that if deserialization fails due to other fields, we can still provide the changelogs
14+
/// to allow developers to potentially find the reason for the failure in the logs..
15+
#[derive(Deserialize, Default)]
16+
pub(crate) struct ChangeIdWrapper {
17+
#[serde(alias = "change-id", default, deserialize_with = "deserialize_change_id")]
18+
pub(crate) inner: Option<ChangeId>,
19+
}
20+
21+
fn deserialize_change_id<'de, D: Deserializer<'de>>(
22+
deserializer: D,
23+
) -> Result<Option<ChangeId>, D::Error> {
24+
let value = toml::Value::deserialize(deserializer)?;
25+
Ok(match value {
26+
toml::Value::String(s) if s == "ignore" => Some(ChangeId::Ignore),
27+
toml::Value::Integer(i) => Some(ChangeId::Id(i as usize)),
28+
_ => {
29+
return Err(serde::de::Error::custom(
30+
"expected \"ignore\" or an integer for change-id",
31+
));
32+
}
33+
})
34+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! This module defines the `Dist` struct, which represents the `[dist]` table
2+
//! in the `bootstrap.toml` configuration file.
3+
//!
4+
//! The `[dist]` table contains options related to the distribution process,
5+
//! including signing, uploading artifacts, source tarballs, compression settings,
6+
//! and inclusion of specific tools.
7+
8+
use serde::{Deserialize, Deserializer};
9+
10+
use crate::core::config::toml::ReplaceOpt;
11+
use crate::core::config::{Merge, set};
12+
use crate::{Config, HashSet, PathBuf, define_config, exit};
13+
14+
define_config! {
15+
struct Dist {
16+
sign_folder: Option<String> = "sign-folder",
17+
upload_addr: Option<String> = "upload-addr",
18+
src_tarball: Option<bool> = "src-tarball",
19+
compression_formats: Option<Vec<String>> = "compression-formats",
20+
compression_profile: Option<String> = "compression-profile",
21+
include_mingw_linker: Option<bool> = "include-mingw-linker",
22+
vendor: Option<bool> = "vendor",
23+
}
24+
}
25+
26+
impl Config {
27+
/// Applies distribution-related configuration from the `Dist` struct
28+
/// to the global `Config` structure.
29+
pub fn apply_dist_config(&mut self, toml_dist: Option<Dist>) {
30+
if let Some(dist) = toml_dist {
31+
let Dist {
32+
sign_folder,
33+
upload_addr,
34+
src_tarball,
35+
compression_formats,
36+
compression_profile,
37+
include_mingw_linker,
38+
vendor,
39+
} = dist;
40+
self.dist_sign_folder = sign_folder.map(PathBuf::from);
41+
self.dist_upload_addr = upload_addr;
42+
self.dist_compression_formats = compression_formats;
43+
set(&mut self.dist_compression_profile, compression_profile);
44+
set(&mut self.rust_dist_src, src_tarball);
45+
set(&mut self.dist_include_mingw_linker, include_mingw_linker);
46+
self.dist_vendor = vendor.unwrap_or_else(|| {
47+
// If we're building from git or tarball sources, enable it by default.
48+
self.rust_info.is_managed_git_subrepository() || self.rust_info.is_from_tarball()
49+
});
50+
}
51+
}
52+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! This module defines the `Gcc` struct, which represents the `[gcc]` table
2+
//! in the `bootstrap.toml` configuration file.
3+
//!
4+
//! The `[gcc]` table contains options specifically related to building or
5+
//! acquiring the GCC compiler for use within the Rust build process.
6+
7+
use serde::{Deserialize, Deserializer};
8+
9+
use crate::core::config::toml::ReplaceOpt;
10+
use crate::core::config::{GccCiMode, Merge};
11+
use crate::{Config, HashSet, PathBuf, define_config, exit};
12+
13+
define_config! {
14+
/// TOML representation of how the GCC build is configured.
15+
struct Gcc {
16+
download_ci_gcc: Option<bool> = "download-ci-gcc",
17+
}
18+
}
19+
20+
impl Config {
21+
/// Applies GCC-related configuration from the `TomlGcc` struct to the
22+
/// global `Config` structure.
23+
pub fn apply_gcc_config(&mut self, toml_gcc: Option<Gcc>) {
24+
if let Some(gcc) = toml_gcc {
25+
self.gcc_ci_mode = match gcc.download_ci_gcc {
26+
Some(value) => match value {
27+
true => GccCiMode::DownloadFromCi,
28+
false => GccCiMode::BuildLocally,
29+
},
30+
None => GccCiMode::default(),
31+
};
32+
}
33+
}
34+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! This module defines the `Install` struct, which represents the `[install]` table
2+
//! in the `bootstrap.toml` configuration file.
3+
//!
4+
//! The `[install]` table contains options that specify the installation paths
5+
//! for various components of the Rust toolchain. These paths determine where
6+
//! executables, libraries, documentation, and other files will be placed
7+
//! during the `install` stage of the build.
8+
9+
use serde::{Deserialize, Deserializer};
10+
11+
use crate::core::config::toml::ReplaceOpt;
12+
use crate::core::config::{Merge, set};
13+
use crate::{Config, HashSet, PathBuf, define_config, exit};
14+
15+
define_config! {
16+
/// TOML representation of various global install decisions.
17+
struct Install {
18+
prefix: Option<String> = "prefix",
19+
sysconfdir: Option<String> = "sysconfdir",
20+
docdir: Option<String> = "docdir",
21+
bindir: Option<String> = "bindir",
22+
libdir: Option<String> = "libdir",
23+
mandir: Option<String> = "mandir",
24+
datadir: Option<String> = "datadir",
25+
}
26+
}
27+
28+
impl Config {
29+
/// Applies installation-related configuration from the `Install` struct
30+
/// to the global `Config` structure.
31+
pub fn apply_install_config(&mut self, toml_install: Option<Install>) {
32+
if let Some(install) = toml_install {
33+
let Install { prefix, sysconfdir, docdir, bindir, libdir, mandir, datadir } = install;
34+
self.prefix = prefix.map(PathBuf::from);
35+
self.sysconfdir = sysconfdir.map(PathBuf::from);
36+
self.datadir = datadir.map(PathBuf::from);
37+
self.docdir = docdir.map(PathBuf::from);
38+
set(&mut self.bindir, bindir.map(PathBuf::from));
39+
self.libdir = libdir.map(PathBuf::from);
40+
self.mandir = mandir.map(PathBuf::from);
41+
}
42+
}
43+
}
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
//! This module defines the `Llvm` struct, which represents the `[llvm]` table
2+
//! in the `bootstrap.toml` configuration file.
3+
4+
use serde::{Deserialize, Deserializer};
5+
6+
use crate::core::config::toml::{Merge, ReplaceOpt, TomlConfig};
7+
use crate::core::config::{StringOrBool, set};
8+
use crate::{Config, HashMap, HashSet, PathBuf, define_config, exit};
9+
10+
define_config! {
11+
/// TOML representation of how the LLVM build is configured.
12+
struct Llvm {
13+
optimize: Option<bool> = "optimize",
14+
thin_lto: Option<bool> = "thin-lto",
15+
release_debuginfo: Option<bool> = "release-debuginfo",
16+
assertions: Option<bool> = "assertions",
17+
tests: Option<bool> = "tests",
18+
enzyme: Option<bool> = "enzyme",
19+
plugins: Option<bool> = "plugins",
20+
// FIXME: Remove this field at Q2 2025, it has been replaced by build.ccache
21+
ccache: Option<StringOrBool> = "ccache",
22+
static_libstdcpp: Option<bool> = "static-libstdcpp",
23+
libzstd: Option<bool> = "libzstd",
24+
ninja: Option<bool> = "ninja",
25+
targets: Option<String> = "targets",
26+
experimental_targets: Option<String> = "experimental-targets",
27+
link_jobs: Option<u32> = "link-jobs",
28+
link_shared: Option<bool> = "link-shared",
29+
version_suffix: Option<String> = "version-suffix",
30+
clang_cl: Option<String> = "clang-cl",
31+
cflags: Option<String> = "cflags",
32+
cxxflags: Option<String> = "cxxflags",
33+
ldflags: Option<String> = "ldflags",
34+
use_libcxx: Option<bool> = "use-libcxx",
35+
use_linker: Option<String> = "use-linker",
36+
allow_old_toolchain: Option<bool> = "allow-old-toolchain",
37+
offload: Option<bool> = "offload",
38+
polly: Option<bool> = "polly",
39+
clang: Option<bool> = "clang",
40+
enable_warnings: Option<bool> = "enable-warnings",
41+
download_ci_llvm: Option<StringOrBool> = "download-ci-llvm",
42+
build_config: Option<HashMap<String, String>> = "build-config",
43+
}
44+
}
45+
46+
/// Compares the current `Llvm` options against those in the CI LLVM builder and detects any incompatible options.
47+
/// It does this by destructuring the `Llvm` instance to make sure every `Llvm` field is covered and not missing.
48+
#[cfg(not(test))]
49+
pub fn check_incompatible_options_for_ci_llvm(
50+
current_config_toml: TomlConfig,
51+
ci_config_toml: TomlConfig,
52+
) -> Result<(), String> {
53+
macro_rules! err {
54+
($current:expr, $expected:expr) => {
55+
if let Some(current) = &$current {
56+
if Some(current) != $expected.as_ref() {
57+
return Err(format!(
58+
"ERROR: Setting `llvm.{}` is incompatible with `llvm.download-ci-llvm`. \
59+
Current value: {:?}, Expected value(s): {}{:?}",
60+
stringify!($expected).replace("_", "-"),
61+
$current,
62+
if $expected.is_some() { "None/" } else { "" },
63+
$expected,
64+
));
65+
};
66+
};
67+
};
68+
}
69+
70+
macro_rules! warn {
71+
($current:expr, $expected:expr) => {
72+
if let Some(current) = &$current {
73+
if Some(current) != $expected.as_ref() {
74+
println!(
75+
"WARNING: `llvm.{}` has no effect with `llvm.download-ci-llvm`. \
76+
Current value: {:?}, Expected value(s): {}{:?}",
77+
stringify!($expected).replace("_", "-"),
78+
$current,
79+
if $expected.is_some() { "None/" } else { "" },
80+
$expected,
81+
);
82+
};
83+
};
84+
};
85+
}
86+
87+
let (Some(current_llvm_config), Some(ci_llvm_config)) =
88+
(current_config_toml.llvm, ci_config_toml.llvm)
89+
else {
90+
return Ok(());
91+
};
92+
93+
let Llvm {
94+
optimize,
95+
thin_lto,
96+
release_debuginfo,
97+
assertions: _,
98+
tests: _,
99+
plugins,
100+
ccache: _,
101+
static_libstdcpp: _,
102+
libzstd,
103+
ninja: _,
104+
targets,
105+
experimental_targets,
106+
link_jobs: _,
107+
link_shared: _,
108+
version_suffix,
109+
clang_cl,
110+
cflags,
111+
cxxflags,
112+
ldflags,
113+
use_libcxx,
114+
use_linker,
115+
allow_old_toolchain,
116+
offload,
117+
polly,
118+
clang,
119+
enable_warnings,
120+
download_ci_llvm: _,
121+
build_config,
122+
enzyme,
123+
} = ci_llvm_config;
124+
125+
err!(current_llvm_config.optimize, optimize);
126+
err!(current_llvm_config.thin_lto, thin_lto);
127+
err!(current_llvm_config.release_debuginfo, release_debuginfo);
128+
err!(current_llvm_config.libzstd, libzstd);
129+
err!(current_llvm_config.targets, targets);
130+
err!(current_llvm_config.experimental_targets, experimental_targets);
131+
err!(current_llvm_config.clang_cl, clang_cl);
132+
err!(current_llvm_config.version_suffix, version_suffix);
133+
err!(current_llvm_config.cflags, cflags);
134+
err!(current_llvm_config.cxxflags, cxxflags);
135+
err!(current_llvm_config.ldflags, ldflags);
136+
err!(current_llvm_config.use_libcxx, use_libcxx);
137+
err!(current_llvm_config.use_linker, use_linker);
138+
err!(current_llvm_config.allow_old_toolchain, allow_old_toolchain);
139+
err!(current_llvm_config.offload, offload);
140+
err!(current_llvm_config.polly, polly);
141+
err!(current_llvm_config.clang, clang);
142+
err!(current_llvm_config.build_config, build_config);
143+
err!(current_llvm_config.plugins, plugins);
144+
err!(current_llvm_config.enzyme, enzyme);
145+
146+
warn!(current_llvm_config.enable_warnings, enable_warnings);
147+
148+
Ok(())
149+
}
150+
151+
impl Config {
152+
pub fn apply_llvm_config(
153+
&mut self,
154+
toml_llvm: Option<Llvm>,
155+
ccache: &mut Option<StringOrBool>,
156+
) {
157+
let mut llvm_tests = None;
158+
let mut llvm_enzyme = None;
159+
let mut llvm_offload = None;
160+
let mut llvm_plugins = None;
161+
162+
if let Some(llvm) = toml_llvm {
163+
let Llvm {
164+
optimize: optimize_toml,
165+
thin_lto,
166+
release_debuginfo,
167+
assertions: _,
168+
tests,
169+
enzyme,
170+
plugins,
171+
ccache: llvm_ccache,
172+
static_libstdcpp,
173+
libzstd,
174+
ninja,
175+
targets,
176+
experimental_targets,
177+
link_jobs,
178+
link_shared,
179+
version_suffix,
180+
clang_cl,
181+
cflags,
182+
cxxflags,
183+
ldflags,
184+
use_libcxx,
185+
use_linker,
186+
allow_old_toolchain,
187+
offload,
188+
polly,
189+
clang,
190+
enable_warnings,
191+
download_ci_llvm,
192+
build_config,
193+
} = llvm;
194+
if llvm_ccache.is_some() {
195+
eprintln!("Warning: llvm.ccache is deprecated. Use build.ccache instead.");
196+
}
197+
198+
if ccache.is_none() {
199+
*ccache = llvm_ccache;
200+
}
201+
set(&mut self.ninja_in_file, ninja);
202+
llvm_tests = tests;
203+
llvm_enzyme = enzyme;
204+
llvm_offload = offload;
205+
llvm_plugins = plugins;
206+
set(&mut self.llvm_optimize, optimize_toml);
207+
set(&mut self.llvm_thin_lto, thin_lto);
208+
set(&mut self.llvm_release_debuginfo, release_debuginfo);
209+
set(&mut self.llvm_static_stdcpp, static_libstdcpp);
210+
set(&mut self.llvm_libzstd, libzstd);
211+
if let Some(v) = link_shared {
212+
self.llvm_link_shared.set(Some(v));
213+
}
214+
self.llvm_targets.clone_from(&targets);
215+
self.llvm_experimental_targets.clone_from(&experimental_targets);
216+
self.llvm_link_jobs = link_jobs;
217+
self.llvm_version_suffix.clone_from(&version_suffix);
218+
self.llvm_clang_cl.clone_from(&clang_cl);
219+
220+
self.llvm_cflags.clone_from(&cflags);
221+
self.llvm_cxxflags.clone_from(&cxxflags);
222+
self.llvm_ldflags.clone_from(&ldflags);
223+
set(&mut self.llvm_use_libcxx, use_libcxx);
224+
self.llvm_use_linker.clone_from(&use_linker);
225+
self.llvm_allow_old_toolchain = allow_old_toolchain.unwrap_or(false);
226+
self.llvm_offload = offload.unwrap_or(false);
227+
self.llvm_polly = polly.unwrap_or(false);
228+
self.llvm_clang = clang.unwrap_or(false);
229+
self.llvm_enable_warnings = enable_warnings.unwrap_or(false);
230+
self.llvm_build_config = build_config.clone().unwrap_or(Default::default());
231+
232+
self.llvm_from_ci = self.parse_download_ci_llvm(download_ci_llvm, self.llvm_assertions);
233+
234+
if self.llvm_from_ci {
235+
let warn = |option: &str| {
236+
println!(
237+
"WARNING: `{option}` will only be used on `compiler/rustc_llvm` build, not for the LLVM build."
238+
);
239+
println!(
240+
"HELP: To use `{option}` for LLVM builds, set `download-ci-llvm` option to false."
241+
);
242+
};
243+
244+
if static_libstdcpp.is_some() {
245+
warn("static-libstdcpp");
246+
}
247+
248+
if link_shared.is_some() {
249+
warn("link-shared");
250+
}
251+
252+
// FIXME(#129153): instead of all the ad-hoc `download-ci-llvm` checks that follow,
253+
// use the `builder-config` present in tarballs since #128822 to compare the local
254+
// config to the ones used to build the LLVM artifacts on CI, and only notify users
255+
// if they've chosen a different value.
256+
257+
if libzstd.is_some() {
258+
println!(
259+
"WARNING: when using `download-ci-llvm`, the local `llvm.libzstd` option, \
260+
like almost all `llvm.*` options, will be ignored and set by the LLVM CI \
261+
artifacts builder config."
262+
);
263+
println!(
264+
"HELP: To use `llvm.libzstd` for LLVM/LLD builds, set `download-ci-llvm` option to false."
265+
);
266+
}
267+
}
268+
269+
if !self.llvm_from_ci && self.llvm_thin_lto && link_shared.is_none() {
270+
// If we're building with ThinLTO on, by default we want to link
271+
// to LLVM shared, to avoid re-doing ThinLTO (which happens in
272+
// the link step) with each stage.
273+
self.llvm_link_shared.set(Some(true));
274+
}
275+
} else {
276+
self.llvm_from_ci = self.parse_download_ci_llvm(None, false);
277+
}
278+
279+
self.llvm_tests = llvm_tests.unwrap_or(false);
280+
self.llvm_enzyme = llvm_enzyme.unwrap_or(false);
281+
self.llvm_offload = llvm_offload.unwrap_or(false);
282+
self.llvm_plugins = llvm_plugins.unwrap_or(false);
283+
}
284+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
10+
use serde::Deserialize;
11+
use serde_derive::Deserialize;
12+
pub mod build;
13+
pub mod change_id;
14+
pub mod dist;
15+
pub mod gcc;
16+
pub mod install;
17+
pub mod llvm;
18+
pub mod rust;
19+
pub mod target;
20+
21+
use build::Build;
22+
use change_id::{ChangeId, ChangeIdWrapper};
23+
use dist::Dist;
24+
use gcc::Gcc;
25+
use install::Install;
26+
use llvm::Llvm;
27+
use rust::Rust;
28+
use target::TomlTarget;
29+
30+
use crate::core::config::{Merge, ReplaceOpt};
31+
use crate::{Config, HashMap, HashSet, Path, PathBuf, exit, fs, t};
32+
33+
/// Structure of the `bootstrap.toml` file that configuration is read from.
34+
///
35+
/// This structure uses `Decodable` to automatically decode a TOML configuration
36+
/// file into this format, and then this is traversed and written into the above
37+
/// `Config` structure.
38+
#[derive(Deserialize, Default)]
39+
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
40+
pub(crate) struct TomlConfig {
41+
#[serde(flatten)]
42+
pub(crate) change_id: ChangeIdWrapper,
43+
pub(super) build: Option<Build>,
44+
pub(super) install: Option<Install>,
45+
pub(super) llvm: Option<Llvm>,
46+
pub(super) gcc: Option<Gcc>,
47+
pub(super) rust: Option<Rust>,
48+
pub(super) target: Option<HashMap<String, TomlTarget>>,
49+
pub(super) dist: Option<Dist>,
50+
pub(super) profile: Option<String>,
51+
pub(super) include: Option<Vec<PathBuf>>,
52+
}
53+
54+
impl Merge for TomlConfig {
55+
fn merge(
56+
&mut self,
57+
parent_config_path: Option<PathBuf>,
58+
included_extensions: &mut HashSet<PathBuf>,
59+
TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self,
60+
replace: ReplaceOpt,
61+
) {
62+
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
63+
if let Some(new) = y {
64+
if let Some(original) = x {
65+
original.merge(None, &mut Default::default(), new, replace);
66+
} else {
67+
*x = Some(new);
68+
}
69+
}
70+
}
71+
72+
self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace);
73+
self.profile.merge(None, &mut Default::default(), profile, replace);
74+
75+
do_merge(&mut self.build, build, replace);
76+
do_merge(&mut self.install, install, replace);
77+
do_merge(&mut self.llvm, llvm, replace);
78+
do_merge(&mut self.gcc, gcc, replace);
79+
do_merge(&mut self.rust, rust, replace);
80+
do_merge(&mut self.dist, dist, replace);
81+
82+
match (self.target.as_mut(), target) {
83+
(_, None) => {}
84+
(None, Some(target)) => self.target = Some(target),
85+
(Some(original_target), Some(new_target)) => {
86+
for (triple, new) in new_target {
87+
if let Some(original) = original_target.get_mut(&triple) {
88+
original.merge(None, &mut Default::default(), new, replace);
89+
} else {
90+
original_target.insert(triple, new);
91+
}
92+
}
93+
}
94+
}
95+
96+
let parent_dir = parent_config_path
97+
.as_ref()
98+
.and_then(|p| p.parent().map(ToOwned::to_owned))
99+
.unwrap_or_default();
100+
101+
// `include` handled later since we ignore duplicates using `ReplaceOpt::IgnoreDuplicate` to
102+
// keep the upper-level configuration to take precedence.
103+
for include_path in include.clone().unwrap_or_default().iter().rev() {
104+
let include_path = parent_dir.join(include_path);
105+
let include_path = include_path.canonicalize().unwrap_or_else(|e| {
106+
eprintln!("ERROR: Failed to canonicalize '{}' path: {e}", include_path.display());
107+
exit!(2);
108+
});
109+
110+
let included_toml = Config::get_toml_inner(&include_path).unwrap_or_else(|e| {
111+
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
112+
exit!(2);
113+
});
114+
115+
assert!(
116+
included_extensions.insert(include_path.clone()),
117+
"Cyclic inclusion detected: '{}' is being included again before its previous inclusion was fully processed.",
118+
include_path.display()
119+
);
120+
121+
self.merge(
122+
Some(include_path.clone()),
123+
included_extensions,
124+
included_toml,
125+
// Ensures that parent configuration always takes precedence
126+
// over child configurations.
127+
ReplaceOpt::IgnoreDuplicate,
128+
);
129+
130+
included_extensions.remove(&include_path);
131+
}
132+
}
133+
}
134+
135+
/// This file is embedded in the overlay directory of the tarball sources. It is
136+
/// useful in scenarios where developers want to see how the tarball sources were
137+
/// generated.
138+
///
139+
/// We also use this file to compare the host's bootstrap.toml against the CI rustc builder
140+
/// configuration to detect any incompatible options.
141+
pub const BUILDER_CONFIG_FILENAME: &str = "builder-config";
142+
143+
impl Config {
144+
pub(crate) fn get_builder_toml(&self, build_name: &str) -> Result<TomlConfig, toml::de::Error> {
145+
if self.dry_run() {
146+
return Ok(TomlConfig::default());
147+
}
148+
149+
let builder_config_path =
150+
self.out.join(self.build.triple).join(build_name).join(BUILDER_CONFIG_FILENAME);
151+
Self::get_toml(&builder_config_path)
152+
}
153+
154+
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
155+
#[cfg(test)]
156+
return Ok(TomlConfig::default());
157+
158+
#[cfg(not(test))]
159+
Self::get_toml_inner(file)
160+
}
161+
162+
pub(crate) fn get_toml_inner(file: &Path) -> Result<TomlConfig, toml::de::Error> {
163+
let contents =
164+
t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
165+
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
166+
// TomlConfig and sub types to be monomorphized 5x by toml.
167+
toml::from_str(&contents)
168+
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
169+
.inspect_err(|_| {
170+
if let Ok(ChangeIdWrapper { inner: Some(ChangeId::Id(id)) }) =
171+
toml::from_str::<toml::Value>(&contents)
172+
.and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
173+
{
174+
let changes = crate::find_recent_config_change_ids(id);
175+
if !changes.is_empty() {
176+
println!(
177+
"WARNING: There have been changes to x.py since you last updated:\n{}",
178+
crate::human_readable_changes(changes)
179+
);
180+
}
181+
}
182+
})
183+
}
184+
}

‎src/bootstrap/src/core/config/toml/rust.rs

Lines changed: 664 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! This module defines the structures and logic for handling target-specific configuration
2+
//! within the `bootstrap.toml` file. This allows you to customize build settings, tools,
3+
//! and flags for individual compilation targets.
4+
//!
5+
//! It includes:
6+
//!
7+
//! * [`TomlTarget`]: This struct directly mirrors the `[target.<triple>]` sections in your
8+
//! `bootstrap.toml`. It's used for deserializing raw TOML data for a specific target.
9+
//! * [`Target`]: This struct represents the processed and validated configuration for a
10+
//! build target, which is is stored in the main [`Config`] structure.
11+
//! * [`Config::apply_target_config`]: This method processes the `TomlTarget` data and
12+
//! applies it to the global [`Config`], ensuring proper path resolution, validation,
13+
//! and integration with other build settings.
14+
15+
use std::collections::HashMap;
16+
17+
use serde::{Deserialize, Deserializer};
18+
19+
use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX;
20+
use crate::core::config::{LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo, StringOrBool};
21+
use crate::{Config, HashSet, PathBuf, TargetSelection, define_config, exit};
22+
23+
define_config! {
24+
/// TOML representation of how each build target is configured.
25+
struct TomlTarget {
26+
cc: Option<String> = "cc",
27+
cxx: Option<String> = "cxx",
28+
ar: Option<String> = "ar",
29+
ranlib: Option<String> = "ranlib",
30+
default_linker: Option<PathBuf> = "default-linker",
31+
linker: Option<String> = "linker",
32+
split_debuginfo: Option<String> = "split-debuginfo",
33+
llvm_config: Option<String> = "llvm-config",
34+
llvm_has_rust_patches: Option<bool> = "llvm-has-rust-patches",
35+
llvm_filecheck: Option<String> = "llvm-filecheck",
36+
llvm_libunwind: Option<String> = "llvm-libunwind",
37+
sanitizers: Option<bool> = "sanitizers",
38+
profiler: Option<StringOrBool> = "profiler",
39+
rpath: Option<bool> = "rpath",
40+
crt_static: Option<bool> = "crt-static",
41+
musl_root: Option<String> = "musl-root",
42+
musl_libdir: Option<String> = "musl-libdir",
43+
wasi_root: Option<String> = "wasi-root",
44+
qemu_rootfs: Option<String> = "qemu-rootfs",
45+
no_std: Option<bool> = "no-std",
46+
codegen_backends: Option<Vec<String>> = "codegen-backends",
47+
runner: Option<String> = "runner",
48+
optimized_compiler_builtins: Option<bool> = "optimized-compiler-builtins",
49+
jemalloc: Option<bool> = "jemalloc",
50+
}
51+
}
52+
53+
/// Per-target configuration stored in the global configuration structure.
54+
#[derive(Debug, Default, Clone, PartialEq, Eq)]
55+
pub struct Target {
56+
/// Some(path to llvm-config) if using an external LLVM.
57+
pub llvm_config: Option<PathBuf>,
58+
pub llvm_has_rust_patches: Option<bool>,
59+
/// Some(path to FileCheck) if one was specified.
60+
pub llvm_filecheck: Option<PathBuf>,
61+
pub llvm_libunwind: Option<LlvmLibunwind>,
62+
pub cc: Option<PathBuf>,
63+
pub cxx: Option<PathBuf>,
64+
pub ar: Option<PathBuf>,
65+
pub ranlib: Option<PathBuf>,
66+
pub default_linker: Option<PathBuf>,
67+
pub linker: Option<PathBuf>,
68+
pub split_debuginfo: Option<SplitDebuginfo>,
69+
pub sanitizers: Option<bool>,
70+
pub profiler: Option<StringOrBool>,
71+
pub rpath: Option<bool>,
72+
pub crt_static: Option<bool>,
73+
pub musl_root: Option<PathBuf>,
74+
pub musl_libdir: Option<PathBuf>,
75+
pub wasi_root: Option<PathBuf>,
76+
pub qemu_rootfs: Option<PathBuf>,
77+
pub runner: Option<String>,
78+
pub no_std: bool,
79+
pub codegen_backends: Option<Vec<String>>,
80+
pub optimized_compiler_builtins: Option<bool>,
81+
pub jemalloc: Option<bool>,
82+
}
83+
84+
impl Target {
85+
pub fn from_triple(triple: &str) -> Self {
86+
let mut target: Self = Default::default();
87+
if triple.contains("-none") || triple.contains("nvptx") || triple.contains("switch") {
88+
target.no_std = true;
89+
}
90+
if triple.contains("emscripten") {
91+
target.runner = Some("node".into());
92+
}
93+
target
94+
}
95+
}
96+
97+
impl Config {
98+
pub fn apply_target_config(&mut self, toml_target: Option<HashMap<String, TomlTarget>>) {
99+
if let Some(t) = toml_target {
100+
for (triple, cfg) in t {
101+
let mut target = Target::from_triple(&triple);
102+
103+
if let Some(ref s) = cfg.llvm_config {
104+
if self.download_rustc_commit.is_some() && triple == *self.build.triple {
105+
panic!(
106+
"setting llvm_config for the host is incompatible with download-rustc"
107+
);
108+
}
109+
target.llvm_config = Some(self.src.join(s));
110+
}
111+
if let Some(patches) = cfg.llvm_has_rust_patches {
112+
assert!(
113+
self.submodules == Some(false) || cfg.llvm_config.is_some(),
114+
"use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided"
115+
);
116+
target.llvm_has_rust_patches = Some(patches);
117+
}
118+
if let Some(ref s) = cfg.llvm_filecheck {
119+
target.llvm_filecheck = Some(self.src.join(s));
120+
}
121+
target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| {
122+
v.parse().unwrap_or_else(|_| {
123+
panic!("failed to parse target.{triple}.llvm-libunwind")
124+
})
125+
});
126+
if let Some(s) = cfg.no_std {
127+
target.no_std = s;
128+
}
129+
target.cc = cfg.cc.map(PathBuf::from);
130+
target.cxx = cfg.cxx.map(PathBuf::from);
131+
target.ar = cfg.ar.map(PathBuf::from);
132+
target.ranlib = cfg.ranlib.map(PathBuf::from);
133+
target.linker = cfg.linker.map(PathBuf::from);
134+
target.crt_static = cfg.crt_static;
135+
target.musl_root = cfg.musl_root.map(PathBuf::from);
136+
target.musl_libdir = cfg.musl_libdir.map(PathBuf::from);
137+
target.wasi_root = cfg.wasi_root.map(PathBuf::from);
138+
target.qemu_rootfs = cfg.qemu_rootfs.map(PathBuf::from);
139+
target.runner = cfg.runner;
140+
target.sanitizers = cfg.sanitizers;
141+
target.profiler = cfg.profiler;
142+
target.rpath = cfg.rpath;
143+
target.optimized_compiler_builtins = cfg.optimized_compiler_builtins;
144+
target.jemalloc = cfg.jemalloc;
145+
146+
if let Some(ref backends) = cfg.codegen_backends {
147+
let available_backends = ["llvm", "cranelift", "gcc"];
148+
149+
target.codegen_backends = Some(backends.iter().map(|s| {
150+
if let Some(backend) = s.strip_prefix(CODEGEN_BACKEND_PREFIX) {
151+
if available_backends.contains(&backend) {
152+
panic!("Invalid value '{s}' for 'target.{triple}.codegen-backends'. Instead, please use '{backend}'.");
153+
} else {
154+
println!("HELP: '{s}' for 'target.{triple}.codegen-backends' might fail. \
155+
Codegen backends are mostly defined without the '{CODEGEN_BACKEND_PREFIX}' prefix. \
156+
In this case, it would be referred to as '{backend}'.");
157+
}
158+
}
159+
160+
s.clone()
161+
}).collect());
162+
}
163+
164+
target.split_debuginfo = cfg.split_debuginfo.as_ref().map(|v| {
165+
v.parse().unwrap_or_else(|_| {
166+
panic!("invalid value for target.{triple}.split-debuginfo")
167+
})
168+
});
169+
170+
self.target_config.insert(TargetSelection::from_user(&triple), target);
171+
}
172+
}
173+
}
174+
}

‎src/bootstrap/src/core/download.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ download-rustc = false
727727
use build_helper::git::PathFreshness;
728728

729729
use crate::core::build_steps::llvm::detect_llvm_freshness;
730-
use crate::core::config::check_incompatible_options_for_ci_llvm;
730+
use crate::core::config::toml::llvm::check_incompatible_options_for_ci_llvm;
731731

732732
if !self.llvm_from_ci {
733733
return;

0 commit comments

Comments
 (0)
Please sign in to comment.