Skip to content

Commit 27b93da

Browse files
committed
Auto merge of rust-lang#129052 - onur-ozkan:better-incompatibility-check, r=Kobzol
detect incompatible CI rustc options more precisely Previously, the logic here was simply checking whether the option was set in `config.toml`. This approach was not manageable in our CI runners as we set so many options in config.toml. In reality, those values are not incompatible since they are usually the same value used to generate the CI rustc. Now, the new logic compares the configuration values with the values used to generate the CI rustc, so we get more precise results and make the process more manageable. r? Kobzol Blocker for rust-lang#122709
2 parents 8fbdc04 + 469d593 commit 27b93da

File tree

4 files changed

+164
-98
lines changed

4 files changed

+164
-98
lines changed

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

+150-86
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ macro_rules! check_ci_llvm {
3636
};
3737
}
3838

39+
/// This file is embedded in the overlay directory of the tarball sources. It is
40+
/// useful in scenarios where developers want to see how the tarball sources were
41+
/// generated.
42+
///
43+
/// We also use this file to compare the host's config.toml against the CI rustc builder
44+
/// configuration to detect any incompatible options.
45+
pub(crate) const BUILDER_CONFIG_FILENAME: &str = "builder-config";
46+
3947
#[derive(Clone, Default)]
4048
pub enum DryRun {
4149
/// This isn't a dry run.
@@ -47,7 +55,7 @@ pub enum DryRun {
4755
UserSelected,
4856
}
4957

50-
#[derive(Copy, Clone, Default, PartialEq, Eq)]
58+
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
5159
pub enum DebuginfoLevel {
5260
#[default]
5361
None,
@@ -117,7 +125,7 @@ impl Display for DebuginfoLevel {
117125
/// 2) MSVC
118126
/// - Self-contained: `-Clinker=<path to rust-lld>`
119127
/// - External: `-Clinker=lld`
120-
#[derive(Default, Copy, Clone)]
128+
#[derive(Copy, Clone, Default, Debug, PartialEq)]
121129
pub enum LldMode {
122130
/// Do not use LLD
123131
#[default]
@@ -1203,40 +1211,42 @@ impl Config {
12031211
}
12041212
}
12051213

1206-
pub fn parse(flags: Flags) -> Config {
1207-
#[cfg(test)]
1208-
fn get_toml(_: &Path) -> TomlConfig {
1209-
TomlConfig::default()
1210-
}
1214+
#[cfg(test)]
1215+
fn get_toml(_: &Path) -> Result<TomlConfig, toml::de::Error> {
1216+
Ok(TomlConfig::default())
1217+
}
12111218

1212-
#[cfg(not(test))]
1213-
fn get_toml(file: &Path) -> TomlConfig {
1214-
let contents =
1215-
t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
1216-
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
1217-
// TomlConfig and sub types to be monomorphized 5x by toml.
1218-
toml::from_str(&contents)
1219-
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
1220-
.unwrap_or_else(|err| {
1221-
if let Ok(Some(changes)) = toml::from_str(&contents)
1222-
.and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table)).map(|change_id| change_id.inner.map(crate::find_recent_config_change_ids))
1223-
{
1224-
if !changes.is_empty() {
1225-
println!(
1226-
"WARNING: There have been changes to x.py since you last updated:\n{}",
1227-
crate::human_readable_changes(&changes)
1228-
);
1229-
}
1219+
#[cfg(not(test))]
1220+
fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
1221+
let contents =
1222+
t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
1223+
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
1224+
// TomlConfig and sub types to be monomorphized 5x by toml.
1225+
toml::from_str(&contents)
1226+
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
1227+
.inspect_err(|_| {
1228+
if let Ok(Some(changes)) = toml::from_str(&contents)
1229+
.and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
1230+
.map(|change_id| change_id.inner.map(crate::find_recent_config_change_ids))
1231+
{
1232+
if !changes.is_empty() {
1233+
println!(
1234+
"WARNING: There have been changes to x.py since you last updated:\n{}",
1235+
crate::human_readable_changes(&changes)
1236+
);
12301237
}
1238+
}
1239+
})
1240+
}
12311241

1232-
eprintln!("failed to parse TOML configuration '{}': {err}", file.display());
1233-
exit!(2);
1234-
})
1235-
}
1236-
Self::parse_inner(flags, get_toml)
1242+
pub fn parse(flags: Flags) -> Config {
1243+
Self::parse_inner(flags, Self::get_toml)
12371244
}
12381245

1239-
pub(crate) fn parse_inner(mut flags: Flags, get_toml: impl Fn(&Path) -> TomlConfig) -> Config {
1246+
pub(crate) fn parse_inner(
1247+
mut flags: Flags,
1248+
get_toml: impl Fn(&Path) -> Result<TomlConfig, toml::de::Error>,
1249+
) -> Config {
12401250
let mut config = Config::default_opts();
12411251

12421252
// Set flags.
@@ -1344,7 +1354,10 @@ impl Config {
13441354
} else {
13451355
toml_path.clone()
13461356
});
1347-
get_toml(&toml_path)
1357+
get_toml(&toml_path).unwrap_or_else(|e| {
1358+
eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
1359+
exit!(2);
1360+
})
13481361
} else {
13491362
config.config = None;
13501363
TomlConfig::default()
@@ -1375,7 +1388,13 @@ impl Config {
13751388
include_path.push("bootstrap");
13761389
include_path.push("defaults");
13771390
include_path.push(format!("config.{include}.toml"));
1378-
let included_toml = get_toml(&include_path);
1391+
let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
1392+
eprintln!(
1393+
"ERROR: Failed to parse default config profile at '{}': {e}",
1394+
include_path.display()
1395+
);
1396+
exit!(2);
1397+
});
13791398
toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate);
13801399
}
13811400

@@ -1591,24 +1610,6 @@ impl Config {
15911610
let mut is_user_configured_rust_channel = false;
15921611

15931612
if let Some(rust) = toml.rust {
1594-
if let Some(commit) = config.download_ci_rustc_commit(rust.download_rustc.clone()) {
1595-
// Primarily used by CI runners to avoid handling download-rustc incompatible
1596-
// options one by one on shell scripts.
1597-
let disable_ci_rustc_if_incompatible =
1598-
env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE")
1599-
.is_some_and(|s| s == "1" || s == "true");
1600-
1601-
if let Err(e) = check_incompatible_options_for_ci_rustc(&rust) {
1602-
if disable_ci_rustc_if_incompatible {
1603-
config.download_rustc_commit = None;
1604-
} else {
1605-
panic!("{}", e);
1606-
}
1607-
} else {
1608-
config.download_rustc_commit = Some(commit);
1609-
}
1610-
}
1611-
16121613
let Rust {
16131614
optimize: optimize_toml,
16141615
debug: debug_toml,
@@ -1656,7 +1657,7 @@ impl Config {
16561657
new_symbol_mangling,
16571658
profile_generate,
16581659
profile_use,
1659-
download_rustc: _,
1660+
download_rustc,
16601661
lto,
16611662
validate_mir_opts,
16621663
frame_pointers,
@@ -1668,6 +1669,8 @@ impl Config {
16681669
is_user_configured_rust_channel = channel.is_some();
16691670
set(&mut config.channel, channel.clone());
16701671

1672+
config.download_rustc_commit = config.download_ci_rustc_commit(download_rustc);
1673+
16711674
debug = debug_toml;
16721675
debug_assertions = debug_assertions_toml;
16731676
debug_assertions_std = debug_assertions_std_toml;
@@ -2345,6 +2348,45 @@ impl Config {
23452348
None => None,
23462349
Some(commit) => {
23472350
self.download_ci_rustc(commit);
2351+
2352+
if let Some(config_path) = &self.config {
2353+
let builder_config_path =
2354+
self.out.join(self.build.triple).join("ci-rustc").join(BUILDER_CONFIG_FILENAME);
2355+
2356+
let ci_config_toml = match Self::get_toml(&builder_config_path) {
2357+
Ok(ci_config_toml) => ci_config_toml,
2358+
Err(e) if e.to_string().contains("unknown field") => {
2359+
println!("WARNING: CI rustc has some fields that are no longer supported in bootstrap; download-rustc will be disabled.");
2360+
println!("HELP: Consider rebasing to a newer commit if available.");
2361+
return None;
2362+
},
2363+
Err(e) => {
2364+
eprintln!("ERROR: Failed to parse CI rustc config at '{}': {e}", builder_config_path.display());
2365+
exit!(2);
2366+
},
2367+
};
2368+
2369+
let current_config_toml = Self::get_toml(config_path).unwrap();
2370+
2371+
// Check the config compatibility
2372+
// FIXME: this doesn't cover `--set` flags yet.
2373+
let res = check_incompatible_options_for_ci_rustc(
2374+
current_config_toml,
2375+
ci_config_toml,
2376+
);
2377+
2378+
let disable_ci_rustc_if_incompatible =
2379+
env::var_os("DISABLE_CI_RUSTC_IF_INCOMPATIBLE")
2380+
.is_some_and(|s| s == "1" || s == "true");
2381+
2382+
if disable_ci_rustc_if_incompatible && res.is_err() {
2383+
println!("WARNING: download-rustc is disabled with `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` env.");
2384+
return None;
2385+
}
2386+
2387+
res.unwrap();
2388+
}
2389+
23482390
Some(commit.clone())
23492391
}
23502392
})
@@ -2662,31 +2704,52 @@ impl Config {
26622704
}
26632705
}
26642706

2665-
/// Checks the CI rustc incompatible options by destructuring the `Rust` instance
2666-
/// and makes sure that no rust options from config.toml are missed.
2667-
fn check_incompatible_options_for_ci_rustc(rust: &Rust) -> Result<(), String> {
2707+
/// Compares the current Rust options against those in the CI rustc builder and detects any incompatible options.
2708+
/// It does this by destructuring the `Rust` instance to make sure every `Rust` field is covered and not missing.
2709+
fn check_incompatible_options_for_ci_rustc(
2710+
current_config_toml: TomlConfig,
2711+
ci_config_toml: TomlConfig,
2712+
) -> Result<(), String> {
26682713
macro_rules! err {
2669-
($name:expr) => {
2670-
if $name.is_some() {
2671-
return Err(format!(
2672-
"ERROR: Setting `rust.{}` is incompatible with `rust.download-rustc`.",
2673-
stringify!($name).replace("_", "-")
2674-
));
2675-
}
2714+
($current:expr, $expected:expr) => {
2715+
if let Some(current) = &$current {
2716+
if Some(current) != $expected.as_ref() {
2717+
return Err(format!(
2718+
"ERROR: Setting `rust.{}` is incompatible with `rust.download-rustc`. \
2719+
Current value: {:?}, Expected value(s): {}{:?}",
2720+
stringify!($expected).replace("_", "-"),
2721+
$current,
2722+
if $expected.is_some() { "None/" } else { "" },
2723+
$expected,
2724+
));
2725+
};
2726+
};
26762727
};
26772728
}
26782729

26792730
macro_rules! warn {
2680-
($name:expr) => {
2681-
if $name.is_some() {
2682-
println!(
2683-
"WARNING: `rust.{}` has no effect with `rust.download-rustc`.",
2684-
stringify!($name).replace("_", "-")
2685-
);
2686-
}
2731+
($current:expr, $expected:expr) => {
2732+
if let Some(current) = &$current {
2733+
if Some(current) != $expected.as_ref() {
2734+
println!(
2735+
"WARNING: `rust.{}` has no effect with `rust.download-rustc`. \
2736+
Current value: {:?}, Expected value(s): {}{:?}",
2737+
stringify!($expected).replace("_", "-"),
2738+
$current,
2739+
if $expected.is_some() { "None/" } else { "" },
2740+
$expected,
2741+
);
2742+
};
2743+
};
26872744
};
26882745
}
26892746

2747+
let (Some(current_rust_config), Some(ci_rust_config)) =
2748+
(current_config_toml.rust, ci_config_toml.rust)
2749+
else {
2750+
return Ok(());
2751+
};
2752+
26902753
let Rust {
26912754
// Following options are the CI rustc incompatible ones.
26922755
optimize,
@@ -2744,30 +2807,31 @@ fn check_incompatible_options_for_ci_rustc(rust: &Rust) -> Result<(), String> {
27442807
download_rustc: _,
27452808
validate_mir_opts: _,
27462809
frame_pointers: _,
2747-
} = rust;
2810+
} = ci_rust_config;
27482811

27492812
// There are two kinds of checks for CI rustc incompatible options:
27502813
// 1. Checking an option that may change the compiler behaviour/output.
27512814
// 2. Checking an option that have no effect on the compiler behaviour/output.
27522815
//
27532816
// If the option belongs to the first category, we call `err` macro for a hard error;
27542817
// otherwise, we just print a warning with `warn` macro.
2755-
err!(optimize);
2756-
err!(debug_logging);
2757-
err!(debuginfo_level_rustc);
2758-
err!(default_linker);
2759-
err!(rpath);
2760-
err!(strip);
2761-
err!(stack_protector);
2762-
err!(lld_mode);
2763-
err!(llvm_tools);
2764-
err!(llvm_bitcode_linker);
2765-
err!(jemalloc);
2766-
err!(lto);
2767-
2768-
warn!(channel);
2769-
warn!(description);
2770-
warn!(incremental);
2818+
2819+
err!(current_rust_config.optimize, optimize);
2820+
err!(current_rust_config.debug_logging, debug_logging);
2821+
err!(current_rust_config.debuginfo_level_rustc, debuginfo_level_rustc);
2822+
err!(current_rust_config.rpath, rpath);
2823+
err!(current_rust_config.strip, strip);
2824+
err!(current_rust_config.lld_mode, lld_mode);
2825+
err!(current_rust_config.llvm_tools, llvm_tools);
2826+
err!(current_rust_config.llvm_bitcode_linker, llvm_bitcode_linker);
2827+
err!(current_rust_config.jemalloc, jemalloc);
2828+
err!(current_rust_config.default_linker, default_linker);
2829+
err!(current_rust_config.stack_protector, stack_protector);
2830+
err!(current_rust_config.lto, lto);
2831+
2832+
warn!(current_rust_config.channel, channel);
2833+
warn!(current_rust_config.description, description);
2834+
warn!(current_rust_config.incremental, incremental);
27712835

27722836
Ok(())
27732837
}

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

+4-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::core::config::{LldMode, Target, TargetSelection, TomlConfig};
1414
fn parse(config: &str) -> Config {
1515
Config::parse_inner(
1616
Flags::parse(&["check".to_string(), "--config=/does/not/exist".to_string()]),
17-
|&_| toml::from_str(&config).unwrap(),
17+
|&_| toml::from_str(&config),
1818
)
1919
}
2020

@@ -151,7 +151,6 @@ runner = "x86_64-runner"
151151
152152
"#,
153153
)
154-
.unwrap()
155154
},
156155
);
157156
assert_eq!(config.change_id, Some(1), "setting top-level value");
@@ -208,13 +207,13 @@ fn override_toml_duplicate() {
208207
"--set=change-id=1".to_owned(),
209208
"--set=change-id=2".to_owned(),
210209
]),
211-
|&_| toml::from_str("change-id = 0").unwrap(),
210+
|&_| toml::from_str("change-id = 0"),
212211
);
213212
}
214213

215214
#[test]
216215
fn profile_user_dist() {
217-
fn get_toml(file: &Path) -> TomlConfig {
216+
fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
218217
let contents =
219218
if file.ends_with("config.toml") || env::var_os("RUST_BOOTSTRAP_CONFIG").is_some() {
220219
"profile = \"user\"".to_owned()
@@ -223,9 +222,7 @@ fn profile_user_dist() {
223222
std::fs::read_to_string(file).unwrap()
224223
};
225224

226-
toml::from_str(&contents)
227-
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
228-
.unwrap()
225+
toml::from_str(&contents).and_then(|table: toml::Value| TomlConfig::deserialize(table))
229226
}
230227
Config::parse_inner(Flags::parse(&["check".to_owned()]), get_toml);
231228
}

0 commit comments

Comments
 (0)