feat(config): Add ChangeId enum for suppressing warnings

Introduces the `ChangeId` enum to allow suppressing `change_id` warnings.
Now, `ChangeId` supports both numeric values and the string literal `"ignore"`.
Numeric values behave as expected, while `"ignore"` is used to suppress warning messages.
This commit is contained in:
bit-aloo 2025-03-26 18:12:21 +05:30
parent 7d49ae9731
commit eaa0613e8d
No known key found for this signature in database
GPG key ID: 02911B24FDAE81DA
4 changed files with 75 additions and 53 deletions

View file

@ -11,8 +11,8 @@ use std::str::FromStr;
use std::{env, process};
use bootstrap::{
Build, CONFIG_CHANGE_HISTORY, Config, Flags, Subcommand, debug, find_recent_config_change_ids,
human_readable_changes, t,
Build, CONFIG_CHANGE_HISTORY, ChangeId, Config, Flags, Subcommand, debug,
find_recent_config_change_ids, human_readable_changes, t,
};
#[cfg(feature = "tracing")]
use tracing::instrument;
@ -155,51 +155,53 @@ fn check_version(config: &Config) -> Option<String> {
let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id;
let warned_id_path = config.out.join("bootstrap").join(".last-warned-change-id");
if let Some(mut id) = config.change_id {
if id == latest_change_id {
return None;
let mut id = match config.change_id {
Some(ChangeId::Id(id)) if id == latest_change_id => return None,
Some(ChangeId::Ignore) => return None,
Some(ChangeId::Id(id)) => id,
None => {
msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n");
msg.push_str("NOTE: to silence this warning, ");
msg.push_str(&format!(
"add `change-id = {latest_change_id}` at the top of `bootstrap.toml`"
));
return Some(msg);
}
// Always try to use `change-id` from .last-warned-change-id first. If it doesn't exist,
// then use the one from the bootstrap.toml. This way we never show the same warnings
// more than once.
if let Ok(t) = fs::read_to_string(&warned_id_path) {
let last_warned_id = usize::from_str(&t)
.unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display()));
// We only use the last_warned_id if it exists in `CONFIG_CHANGE_HISTORY`.
// Otherwise, we may retrieve all the changes if it's not the highest value.
// For better understanding, refer to `change_tracker::find_recent_config_change_ids`.
if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) {
id = last_warned_id;
}
};
let changes = find_recent_config_change_ids(id);
if changes.is_empty() {
return None;
}
msg.push_str("There have been changes to x.py since you last updated:\n");
msg.push_str(&human_readable_changes(&changes));
msg.push_str("NOTE: to silence this warning, ");
msg.push_str(&format!(
"update `bootstrap.toml` to use `change-id = {latest_change_id}` instead"
));
if io::stdout().is_terminal() {
t!(fs::write(warned_id_path, latest_change_id.to_string()));
}
} else {
msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n");
msg.push_str("NOTE: to silence this warning, ");
msg.push_str(&format!(
"add `change-id = {latest_change_id}` at the top of `bootstrap.toml`"
));
};
// Always try to use `change-id` from .last-warned-change-id first. If it doesn't exist,
// then use the one from the bootstrap.toml. This way we never show the same warnings
// more than once.
if let Ok(t) = fs::read_to_string(&warned_id_path) {
let last_warned_id = usize::from_str(&t)
.unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display()));
// We only use the last_warned_id if it exists in `CONFIG_CHANGE_HISTORY`.
// Otherwise, we may retrieve all the changes if it's not the highest value.
// For better understanding, refer to `change_tracker::find_recent_config_change_ids`.
if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) {
id = last_warned_id;
}
};
let changes = find_recent_config_change_ids(id);
if changes.is_empty() {
return None;
}
msg.push_str("There have been changes to x.py since you last updated:\n");
msg.push_str(&human_readable_changes(&changes));
msg.push_str("NOTE: to silence this warning, ");
msg.push_str(&format!(
"update `bootstrap.toml` to use `change-id = {latest_change_id}` instead"
));
if io::stdout().is_terminal() {
t!(fs::write(warned_id_path, latest_change_id.to_string()));
}
Some(msg)
}

View file

@ -193,7 +193,7 @@ pub enum GccCiMode {
/// `bootstrap.example.toml`.
#[derive(Default, Clone)]
pub struct Config {
pub change_id: Option<usize>,
pub change_id: Option<ChangeId>,
pub bypass_bootstrap_lock: bool,
pub ccache: Option<String>,
/// Call Build::ninja() instead of this.
@ -700,14 +700,32 @@ pub(crate) struct TomlConfig {
profile: Option<String>,
}
/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`.
#[derive(Clone, Debug, PartialEq)]
pub enum ChangeId {
Ignore,
Id(usize),
}
/// Since we use `#[serde(deny_unknown_fields)]` on `TomlConfig`, we need a wrapper type
/// for the "change-id" field to parse it even if other fields are invalid. This ensures
/// that if deserialization fails due to other fields, we can still provide the changelogs
/// to allow developers to potentially find the reason for the failure in the logs..
#[derive(Deserialize, Default)]
pub(crate) struct ChangeIdWrapper {
#[serde(alias = "change-id")]
pub(crate) inner: Option<usize>,
#[serde(alias = "change-id", default, deserialize_with = "deserialize_change_id")]
pub(crate) inner: Option<ChangeId>,
}
fn deserialize_change_id<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<ChangeId>, D::Error> {
let value = toml::Value::deserialize(deserializer)?;
Ok(match value {
toml::Value::String(s) if s == "ignore" => Some(ChangeId::Ignore),
toml::Value::Integer(i) => Some(ChangeId::Id(i as usize)),
_ => return Err(serde::de::Error::custom("expected \"ignore\" or an integer")),
})
}
/// Describes how to handle conflicts in merging two [`TomlConfig`]
@ -1351,10 +1369,11 @@ impl Config {
toml::from_str(&contents)
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
.inspect_err(|_| {
if let Ok(Some(changes)) = toml::from_str(&contents)
.and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
.map(|change_id| change_id.inner.map(crate::find_recent_config_change_ids))
if let Ok(ChangeIdWrapper { inner: Some(ChangeId::Id(id)) }) =
toml::from_str::<toml::Value>(&contents)
.and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table))
{
let changes = crate::find_recent_config_change_ids(id);
if !changes.is_empty() {
println!(
"WARNING: There have been changes to x.py since you last updated:\n{}",

View file

@ -10,6 +10,7 @@ use serde::Deserialize;
use super::flags::Flags;
use super::{ChangeIdWrapper, Config, RUSTC_IF_UNCHANGED_ALLOWED_PATHS};
use crate::ChangeId;
use crate::core::build_steps::clippy::{LintConfig, get_clippy_rules_in_order};
use crate::core::build_steps::llvm;
use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS;
@ -171,7 +172,7 @@ runner = "x86_64-runner"
)
},
);
assert_eq!(config.change_id, Some(1), "setting top-level value");
assert_eq!(config.change_id, Some(ChangeId::Id(1)), "setting top-level value");
assert_eq!(
config.rust_lto,
crate::core::config::RustcLto::Fat,
@ -311,7 +312,7 @@ fn parse_change_id_with_unknown_field() {
"#;
let change_id_wrapper: ChangeIdWrapper = toml::from_str(config).unwrap();
assert_eq!(change_id_wrapper.inner, Some(3461));
assert_eq!(change_id_wrapper.inner, Some(ChangeId::Id(3461)));
}
#[test]

View file

@ -45,8 +45,8 @@ mod core;
mod utils;
pub use core::builder::PathSet;
pub use core::config::Config;
pub use core::config::flags::{Flags, Subcommand};
pub use core::config::{ChangeId, Config};
#[cfg(feature = "tracing")]
use tracing::{instrument, span};