Skip to content

Commit 978aa0c

Browse files
authored
Rollup merge of #138934 - onur-ozkan:extended-config-profiles, r=Kobzol
support config extensions _Copied from the `rustc-dev-guide` addition:_ >When working on different tasks, you might need to switch between different bootstrap >configurations. >Sometimes you may want to keep an old configuration for future use. But saving raw config >values in >random files and manually copying and pasting them can quickly become messy, especially if >you have a >long history of different configurations. > >To simplify managing multiple configurations, you can create config extensions. > >For example, you can create a simple config file named `cross.toml`: > >```toml >[build] >build = "x86_64-unknown-linux-gnu" >host = ["i686-unknown-linux-gnu"] >target = ["i686-unknown-linux-gnu"] > > >[llvm] >download-ci-llvm = false > >[target.x86_64-unknown-linux-gnu] >llvm-config = "/path/to/llvm-19/bin/llvm-config" >``` > >Then, include this in your `bootstrap.toml`: > >```toml >include = ["cross.toml"] >``` > >You can also include extensions within extensions recursively. > >**Note:** In the `include` field, the overriding logic follows a right-to-left order. For example, in `include = ["a.toml", "b.toml"]`, extension `b.toml` overrides `a.toml`. Also, parent extensions always overrides the inner ones. try-job: x86_64-mingw-2
2 parents ad10d18 + ac7d1be commit 978aa0c

File tree

5 files changed

+374
-20
lines changed

5 files changed

+374
-20
lines changed

Diff for: bootstrap.example.toml

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
# Note that this has no default value (x.py uses the defaults in `bootstrap.example.toml`).
2020
#profile = <none>
2121

22+
# Inherits configuration values from different configuration files (a.k.a. config extensions).
23+
# Supports absolute paths, and uses the current directory (where the bootstrap was invoked)
24+
# as the base if the given path is not absolute.
25+
#
26+
# The overriding logic follows a right-to-left order. For example, in `include = ["a.toml", "b.toml"]`,
27+
# extension `b.toml` overrides `a.toml`. Also, parent extensions always overrides the inner ones.
28+
#include = []
29+
2230
# Keeps track of major changes made to this configuration.
2331
#
2432
# This value also represents ID of the PR that caused major changes. Meaning,

Diff for: src/bootstrap/src/core/config/config.rs

+115-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use std::cell::{Cell, RefCell};
77
use std::collections::{BTreeSet, HashMap, HashSet};
88
use std::fmt::{self, Display};
9+
use std::hash::Hash;
910
use std::io::IsTerminal;
1011
use std::path::{Path, PathBuf, absolute};
1112
use std::process::Command;
@@ -701,6 +702,7 @@ pub(crate) struct TomlConfig {
701702
target: Option<HashMap<String, TomlTarget>>,
702703
dist: Option<Dist>,
703704
profile: Option<String>,
705+
include: Option<Vec<PathBuf>>,
704706
}
705707

706708
/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`.
@@ -747,27 +749,35 @@ enum ReplaceOpt {
747749
}
748750

749751
trait Merge {
750-
fn merge(&mut self, other: Self, replace: ReplaceOpt);
752+
fn merge(
753+
&mut self,
754+
parent_config_path: Option<PathBuf>,
755+
included_extensions: &mut HashSet<PathBuf>,
756+
other: Self,
757+
replace: ReplaceOpt,
758+
);
751759
}
752760

753761
impl Merge for TomlConfig {
754762
fn merge(
755763
&mut self,
756-
TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id }: Self,
764+
parent_config_path: Option<PathBuf>,
765+
included_extensions: &mut HashSet<PathBuf>,
766+
TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self,
757767
replace: ReplaceOpt,
758768
) {
759769
fn do_merge<T: Merge>(x: &mut Option<T>, y: Option<T>, replace: ReplaceOpt) {
760770
if let Some(new) = y {
761771
if let Some(original) = x {
762-
original.merge(new, replace);
772+
original.merge(None, &mut Default::default(), new, replace);
763773
} else {
764774
*x = Some(new);
765775
}
766776
}
767777
}
768778

769-
self.change_id.inner.merge(change_id.inner, replace);
770-
self.profile.merge(profile, replace);
779+
self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace);
780+
self.profile.merge(None, &mut Default::default(), profile, replace);
771781

772782
do_merge(&mut self.build, build, replace);
773783
do_merge(&mut self.install, install, replace);
@@ -782,13 +792,50 @@ impl Merge for TomlConfig {
782792
(Some(original_target), Some(new_target)) => {
783793
for (triple, new) in new_target {
784794
if let Some(original) = original_target.get_mut(&triple) {
785-
original.merge(new, replace);
795+
original.merge(None, &mut Default::default(), new, replace);
786796
} else {
787797
original_target.insert(triple, new);
788798
}
789799
}
790800
}
791801
}
802+
803+
let parent_dir = parent_config_path
804+
.as_ref()
805+
.and_then(|p| p.parent().map(ToOwned::to_owned))
806+
.unwrap_or_default();
807+
808+
// `include` handled later since we ignore duplicates using `ReplaceOpt::IgnoreDuplicate` to
809+
// keep the upper-level configuration to take precedence.
810+
for include_path in include.clone().unwrap_or_default().iter().rev() {
811+
let include_path = parent_dir.join(include_path);
812+
let include_path = include_path.canonicalize().unwrap_or_else(|e| {
813+
eprintln!("ERROR: Failed to canonicalize '{}' path: {e}", include_path.display());
814+
exit!(2);
815+
});
816+
817+
let included_toml = Config::get_toml_inner(&include_path).unwrap_or_else(|e| {
818+
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
819+
exit!(2);
820+
});
821+
822+
assert!(
823+
included_extensions.insert(include_path.clone()),
824+
"Cyclic inclusion detected: '{}' is being included again before its previous inclusion was fully processed.",
825+
include_path.display()
826+
);
827+
828+
self.merge(
829+
Some(include_path.clone()),
830+
included_extensions,
831+
included_toml,
832+
// Ensures that parent configuration always takes precedence
833+
// over child configurations.
834+
ReplaceOpt::IgnoreDuplicate,
835+
);
836+
837+
included_extensions.remove(&include_path);
838+
}
792839
}
793840
}
794841

@@ -803,7 +850,13 @@ macro_rules! define_config {
803850
}
804851

805852
impl Merge for $name {
806-
fn merge(&mut self, other: Self, replace: ReplaceOpt) {
853+
fn merge(
854+
&mut self,
855+
_parent_config_path: Option<PathBuf>,
856+
_included_extensions: &mut HashSet<PathBuf>,
857+
other: Self,
858+
replace: ReplaceOpt
859+
) {
807860
$(
808861
match replace {
809862
ReplaceOpt::IgnoreDuplicate => {
@@ -903,7 +956,13 @@ macro_rules! define_config {
903956
}
904957

905958
impl<T> Merge for Option<T> {
906-
fn merge(&mut self, other: Self, replace: ReplaceOpt) {
959+
fn merge(
960+
&mut self,
961+
_parent_config_path: Option<PathBuf>,
962+
_included_extensions: &mut HashSet<PathBuf>,
963+
other: Self,
964+
replace: ReplaceOpt,
965+
) {
907966
match replace {
908967
ReplaceOpt::IgnoreDuplicate => {
909968
if self.is_none() {
@@ -1363,13 +1422,15 @@ impl Config {
13631422
Self::get_toml(&builder_config_path)
13641423
}
13651424

1366-
#[cfg(test)]
1367-
pub(crate) fn get_toml(_: &Path) -> Result<TomlConfig, toml::de::Error> {
1368-
Ok(TomlConfig::default())
1425+
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
1426+
#[cfg(test)]
1427+
return Ok(TomlConfig::default());
1428+
1429+
#[cfg(not(test))]
1430+
Self::get_toml_inner(file)
13691431
}
13701432

1371-
#[cfg(not(test))]
1372-
pub(crate) fn get_toml(file: &Path) -> Result<TomlConfig, toml::de::Error> {
1433+
fn get_toml_inner(file: &Path) -> Result<TomlConfig, toml::de::Error> {
13731434
let contents =
13741435
t!(fs::read_to_string(file), format!("config file {} not found", file.display()));
13751436
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
@@ -1548,7 +1609,8 @@ impl Config {
15481609
// but not if `bootstrap.toml` hasn't been created.
15491610
let mut toml = if !using_default_path || toml_path.exists() {
15501611
config.config = Some(if cfg!(not(test)) {
1551-
toml_path.canonicalize().unwrap()
1612+
toml_path = toml_path.canonicalize().unwrap();
1613+
toml_path.clone()
15521614
} else {
15531615
toml_path.clone()
15541616
});
@@ -1576,6 +1638,26 @@ impl Config {
15761638
toml.profile = Some("dist".into());
15771639
}
15781640

1641+
// Reverse the list to ensure the last added config extension remains the most dominant.
1642+
// For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml".
1643+
//
1644+
// This must be handled before applying the `profile` since `include`s should always take
1645+
// precedence over `profile`s.
1646+
for include_path in toml.include.clone().unwrap_or_default().iter().rev() {
1647+
let include_path = toml_path.parent().unwrap().join(include_path);
1648+
1649+
let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
1650+
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
1651+
exit!(2);
1652+
});
1653+
toml.merge(
1654+
Some(include_path),
1655+
&mut Default::default(),
1656+
included_toml,
1657+
ReplaceOpt::IgnoreDuplicate,
1658+
);
1659+
}
1660+
15791661
if let Some(include) = &toml.profile {
15801662
// Allows creating alias for profile names, allowing
15811663
// profiles to be renamed while maintaining back compatibility
@@ -1597,7 +1679,12 @@ impl Config {
15971679
);
15981680
exit!(2);
15991681
});
1600-
toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate);
1682+
toml.merge(
1683+
Some(include_path),
1684+
&mut Default::default(),
1685+
included_toml,
1686+
ReplaceOpt::IgnoreDuplicate,
1687+
);
16011688
}
16021689

16031690
let mut override_toml = TomlConfig::default();
@@ -1608,7 +1695,12 @@ impl Config {
16081695

16091696
let mut err = match get_table(option) {
16101697
Ok(v) => {
1611-
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1698+
override_toml.merge(
1699+
None,
1700+
&mut Default::default(),
1701+
v,
1702+
ReplaceOpt::ErrorOnDuplicate,
1703+
);
16121704
continue;
16131705
}
16141706
Err(e) => e,
@@ -1619,7 +1711,12 @@ impl Config {
16191711
if !value.contains('"') {
16201712
match get_table(&format!(r#"{key}="{value}""#)) {
16211713
Ok(v) => {
1622-
override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate);
1714+
override_toml.merge(
1715+
None,
1716+
&mut Default::default(),
1717+
v,
1718+
ReplaceOpt::ErrorOnDuplicate,
1719+
);
16231720
continue;
16241721
}
16251722
Err(e) => err = e,
@@ -1629,7 +1726,7 @@ impl Config {
16291726
eprintln!("failed to parse override `{option}`: `{err}");
16301727
exit!(2)
16311728
}
1632-
toml.merge(override_toml, ReplaceOpt::Override);
1729+
toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override);
16331730

16341731
config.change_id = toml.change_id.inner;
16351732

0 commit comments

Comments
 (0)