Skip to content

Commit 70db836

Browse files
committed
Auto merge of rust-lang#111566 - clubby789:bootstrap-override-config, r=ozkanonur
Override config.toml options from command line https://rust-lang.zulipchat.com/#narrow/stream/326414-t-infra.2Fbootstrap/topic/Running.20tests.20on.20precompiled.20rustc/near/357763280 cc `@jyn514`
2 parents d69787f + 7a7cbe0 commit 70db836

File tree

6 files changed

+303
-47
lines changed

6 files changed

+303
-47
lines changed

src/bootstrap/config.rs

+124-26
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ impl SplitDebuginfo {
350350
}
351351

352352
/// LTO mode used for compiling rustc itself.
353-
#[derive(Default, Clone, PartialEq)]
353+
#[derive(Default, Clone, PartialEq, Debug)]
354354
pub enum RustcLto {
355355
Off,
356356
#[default]
@@ -508,29 +508,42 @@ struct TomlConfig {
508508
profile: Option<String>,
509509
}
510510

511+
/// Describes how to handle conflicts in merging two [`TomlConfig`]
512+
#[derive(Copy, Clone, Debug)]
513+
enum ReplaceOpt {
514+
/// Silently ignore a duplicated value
515+
IgnoreDuplicate,
516+
/// Override the current value, even if it's `Some`
517+
Override,
518+
/// Exit with an error on duplicate values
519+
ErrorOnDuplicate,
520+
}
521+
511522
trait Merge {
512-
fn merge(&mut self, other: Self);
523+
fn merge(&mut self, other: Self, replace: ReplaceOpt);
513524
}
514525

515526
impl Merge for TomlConfig {
516527
fn merge(
517528
&mut self,
518-
TomlConfig { build, install, llvm, rust, dist, target, profile: _, changelog_seen: _ }: Self,
529+
TomlConfig { build, install, llvm, rust, dist, target, profile: _, changelog_seen }: Self,
530+
replace: ReplaceOpt,
519531
) {
520-
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>) {
532+
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
521533
if let Some(new) = y {
522534
if let Some(original) = x {
523-
original.merge(new);
535+
original.merge(new, replace);
524536
} else {
525537
*x = Some(new);
526538
}
527539
}
528540
}
529-
do_merge(&mut self.build, build);
530-
do_merge(&mut self.install, install);
531-
do_merge(&mut self.llvm, llvm);
532-
do_merge(&mut self.rust, rust);
533-
do_merge(&mut self.dist, dist);
541+
self.changelog_seen.merge(changelog_seen, replace);
542+
do_merge(&mut self.build, build, replace);
543+
do_merge(&mut self.install, install, replace);
544+
do_merge(&mut self.llvm, llvm, replace);
545+
do_merge(&mut self.rust, rust, replace);
546+
do_merge(&mut self.dist, dist, replace);
534547
assert!(target.is_none(), "merging target-specific config is not currently supported");
535548
}
536549
}
@@ -547,10 +560,33 @@ macro_rules! define_config {
547560
}
548561

549562
impl Merge for $name {
550-
fn merge(&mut self, other: Self) {
563+
fn merge(&mut self, other: Self, replace: ReplaceOpt) {
551564
$(
552-
if !self.$field.is_some() {
553-
self.$field = other.$field;
565+
match replace {
566+
ReplaceOpt::IgnoreDuplicate => {
567+
if self.$field.is_none() {
568+
self.$field = other.$field;
569+
}
570+
},
571+
ReplaceOpt::Override => {
572+
if other.$field.is_some() {
573+
self.$field = other.$field;
574+
}
575+
}
576+
ReplaceOpt::ErrorOnDuplicate => {
577+
if other.$field.is_some() {
578+
if self.$field.is_some() {
579+
if cfg!(test) {
580+
panic!("overriding existing option")
581+
} else {
582+
eprintln!("overriding existing option: `{}`", stringify!($field));
583+
crate::detail_exit(2);
584+
}
585+
} else {
586+
self.$field = other.$field;
587+
}
588+
}
589+
}
554590
}
555591
)*
556592
}
@@ -623,6 +659,37 @@ macro_rules! define_config {
623659
}
624660
}
625661

662+
impl<T> Merge for Option<T> {
663+
fn merge(&mut self, other: Self, replace: ReplaceOpt) {
664+
match replace {
665+
ReplaceOpt::IgnoreDuplicate => {
666+
if self.is_none() {
667+
*self = other;
668+
}
669+
}
670+
ReplaceOpt::Override => {
671+
if other.is_some() {
672+
*self = other;
673+
}
674+
}
675+
ReplaceOpt::ErrorOnDuplicate => {
676+
if other.is_some() {
677+
if self.is_some() {
678+
if cfg!(test) {
679+
panic!("overriding existing option")
680+
} else {
681+
eprintln!("overriding existing option");
682+
crate::detail_exit(2);
683+
}
684+
} else {
685+
*self = other;
686+
}
687+
}
688+
}
689+
}
690+
}
691+
}
692+
626693
define_config! {
627694
/// TOML representation of various global build decisions.
628695
#[derive(Default)]
@@ -864,28 +931,27 @@ impl Config {
864931

865932
pub fn parse(args: &[String]) -> Config {
866933
#[cfg(test)]
867-
let get_toml = |_: &_| TomlConfig::default();
934+
fn get_toml(_: &Path) -> TomlConfig {
935+
TomlConfig::default()
936+
}
937+
868938
#[cfg(not(test))]
869-
let get_toml = |file: &Path| {
939+
fn get_toml(file: &Path) -> TomlConfig {
870940
let contents =
871941
t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
872942
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
873943
// TomlConfig and sub types to be monomorphized 5x by toml.
874-
match toml::from_str(&contents)
944+
toml::from_str(&contents)
875945
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
876-
{
877-
Ok(table) => table,
878-
Err(err) => {
879-
eprintln!("failed to parse TOML configuration '{}': {}", file.display(), err);
946+
.unwrap_or_else(|err| {
947+
eprintln!("failed to parse TOML configuration '{}': {err}", file.display());
880948
crate::detail_exit(2);
881-
}
882-
}
883-
};
884-
949+
})
950+
}
885951
Self::parse_inner(args, get_toml)
886952
}
887953

888-
fn parse_inner<'a>(args: &[String], get_toml: impl 'a + Fn(&Path) -> TomlConfig) -> Config {
954+
fn parse_inner(args: &[String], get_toml: impl Fn(&Path) -> TomlConfig) -> Config {
889955
let mut flags = Flags::parse(&args);
890956
let mut config = Config::default_opts();
891957

@@ -998,8 +1064,40 @@ impl Config {
9981064
include_path.push("defaults");
9991065
include_path.push(format!("config.{}.toml", include));
10001066
let included_toml = get_toml(&include_path);
1001-
toml.merge(included_toml);
1067+
toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate);
1068+
}
1069+
1070+
let mut override_toml = TomlConfig::default();
1071+
for option in flags.set.iter() {
1072+
fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
1073+
toml::from_str(&option)
1074+
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
1075+
}
1076+
1077+
let mut err = match get_table(option) {
1078+
Ok(v) => {
1079+
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1080+
continue;
1081+
}
1082+
Err(e) => e,
1083+
};
1084+
// We want to be able to set string values without quotes,
1085+
// like in `configure.py`. Try adding quotes around the right hand side
1086+
if let Some((key, value)) = option.split_once("=") {
1087+
if !value.contains('"') {
1088+
match get_table(&format!(r#"{key}="{value}""#)) {
1089+
Ok(v) => {
1090+
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1091+
continue;
1092+
}
1093+
Err(e) => err = e,
1094+
}
1095+
}
1096+
}
1097+
eprintln!("failed to parse override `{option}`: `{err}");
1098+
crate::detail_exit(2)
10021099
}
1100+
toml.merge(override_toml, ReplaceOpt::Override);
10031101

10041102
config.changelog_seen = toml.changelog_seen;
10051103

src/bootstrap/config/tests.rs

+71-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use super::{Config, Flags, TomlConfig};
1+
use super::{Config, Flags};
22
use clap::CommandFactory;
33
use std::{env, path::Path};
44

5-
fn toml(config: &str) -> impl '_ + Fn(&Path) -> TomlConfig {
6-
|&_| toml::from_str(config).unwrap()
7-
}
8-
95
fn parse(config: &str) -> Config {
10-
Config::parse_inner(&["check".to_owned(), "--config=/does/not/exist".to_owned()], toml(config))
6+
Config::parse_inner(&["check".to_owned(), "--config=/does/not/exist".to_owned()], |&_| {
7+
toml::from_str(config).unwrap()
8+
})
119
}
1210

1311
#[test]
@@ -94,3 +92,70 @@ fn detect_src_and_out() {
9492
fn clap_verify() {
9593
Flags::command().debug_assert();
9694
}
95+
96+
#[test]
97+
fn override_toml() {
98+
let config = Config::parse_inner(
99+
&[
100+
"check".to_owned(),
101+
"--config=/does/not/exist".to_owned(),
102+
"--set=changelog-seen=1".to_owned(),
103+
"--set=rust.lto=fat".to_owned(),
104+
"--set=rust.deny-warnings=false".to_owned(),
105+
"--set=build.gdb=\"bar\"".to_owned(),
106+
"--set=build.tools=[\"cargo\"]".to_owned(),
107+
"--set=llvm.build-config={\"foo\" = \"bar\"}".to_owned(),
108+
],
109+
|&_| {
110+
toml::from_str(
111+
r#"
112+
changelog-seen = 0
113+
[rust]
114+
lto = "off"
115+
deny-warnings = true
116+
117+
[build]
118+
gdb = "foo"
119+
tools = []
120+
121+
[llvm]
122+
download-ci-llvm = false
123+
build-config = {}
124+
"#,
125+
)
126+
.unwrap()
127+
},
128+
);
129+
assert_eq!(config.changelog_seen, Some(1), "setting top-level value");
130+
assert_eq!(
131+
config.rust_lto,
132+
crate::config::RustcLto::Fat,
133+
"setting string value without quotes"
134+
);
135+
assert_eq!(config.gdb, Some("bar".into()), "setting string value with quotes");
136+
assert_eq!(config.deny_warnings, false, "setting boolean value");
137+
assert_eq!(
138+
config.tools,
139+
Some(["cargo".to_string()].into_iter().collect()),
140+
"setting list value"
141+
);
142+
assert_eq!(
143+
config.llvm_build_config,
144+
[("foo".to_string(), "bar".to_string())].into_iter().collect(),
145+
"setting dictionary value"
146+
);
147+
}
148+
149+
#[test]
150+
#[should_panic]
151+
fn override_toml_duplicate() {
152+
Config::parse_inner(
153+
&[
154+
"check".to_owned(),
155+
"--config=/does/not/exist".to_owned(),
156+
"--set=changelog-seen=1".to_owned(),
157+
"--set=changelog-seen=2".to_owned(),
158+
],
159+
|&_| toml::from_str("changelog-seen = 0").unwrap(),
160+
);
161+
}

src/bootstrap/flags.rs

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ pub struct Flags {
158158
#[arg(global(true))]
159159
/// paths for the subcommand
160160
pub paths: Vec<PathBuf>,
161+
/// override options in config.toml
162+
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
163+
pub set: Vec<String>,
161164
/// arguments passed to subcommands
162165
#[arg(global(true), last(true), value_name = "ARGS")]
163166
pub free_args: Vec<String>,

0 commit comments

Comments
 (0)