6
6
use std:: cell:: { Cell , RefCell } ;
7
7
use std:: collections:: { BTreeSet , HashMap , HashSet } ;
8
8
use std:: fmt:: { self , Display } ;
9
+ use std:: hash:: Hash ;
9
10
use std:: io:: IsTerminal ;
10
11
use std:: path:: { Path , PathBuf , absolute} ;
11
12
use std:: process:: Command ;
@@ -701,6 +702,7 @@ pub(crate) struct TomlConfig {
701
702
target : Option < HashMap < String , TomlTarget > > ,
702
703
dist : Option < Dist > ,
703
704
profile : Option < String > ,
705
+ include : Option < Vec < PathBuf > > ,
704
706
}
705
707
706
708
/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`.
@@ -747,27 +749,35 @@ enum ReplaceOpt {
747
749
}
748
750
749
751
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
+ ) ;
751
759
}
752
760
753
761
impl Merge for TomlConfig {
754
762
fn merge (
755
763
& 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 ,
757
767
replace : ReplaceOpt ,
758
768
) {
759
769
fn do_merge < T : Merge > ( x : & mut Option < T > , y : Option < T > , replace : ReplaceOpt ) {
760
770
if let Some ( new) = y {
761
771
if let Some ( original) = x {
762
- original. merge ( new, replace) ;
772
+ original. merge ( None , & mut Default :: default ( ) , new, replace) ;
763
773
} else {
764
774
* x = Some ( new) ;
765
775
}
766
776
}
767
777
}
768
778
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) ;
771
781
772
782
do_merge ( & mut self . build , build, replace) ;
773
783
do_merge ( & mut self . install , install, replace) ;
@@ -782,13 +792,50 @@ impl Merge for TomlConfig {
782
792
( Some ( original_target) , Some ( new_target) ) => {
783
793
for ( triple, new) in new_target {
784
794
if let Some ( original) = original_target. get_mut ( & triple) {
785
- original. merge ( new, replace) ;
795
+ original. merge ( None , & mut Default :: default ( ) , new, replace) ;
786
796
} else {
787
797
original_target. insert ( triple, new) ;
788
798
}
789
799
}
790
800
}
791
801
}
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
+ }
792
839
}
793
840
}
794
841
@@ -803,7 +850,13 @@ macro_rules! define_config {
803
850
}
804
851
805
852
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
+ ) {
807
860
$(
808
861
match replace {
809
862
ReplaceOpt :: IgnoreDuplicate => {
@@ -903,7 +956,13 @@ macro_rules! define_config {
903
956
}
904
957
905
958
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
+ ) {
907
966
match replace {
908
967
ReplaceOpt :: IgnoreDuplicate => {
909
968
if self . is_none ( ) {
@@ -1363,13 +1422,15 @@ impl Config {
1363
1422
Self :: get_toml ( & builder_config_path)
1364
1423
}
1365
1424
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)
1369
1431
}
1370
1432
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 > {
1373
1434
let contents =
1374
1435
t ! ( fs:: read_to_string( file) , format!( "config file {} not found" , file. display( ) ) ) ;
1375
1436
// Deserialize to Value and then TomlConfig to prevent the Deserialize impl of
@@ -1548,7 +1609,8 @@ impl Config {
1548
1609
// but not if `bootstrap.toml` hasn't been created.
1549
1610
let mut toml = if !using_default_path || toml_path. exists ( ) {
1550
1611
config. config = Some ( if cfg ! ( not( test) ) {
1551
- toml_path. canonicalize ( ) . unwrap ( )
1612
+ toml_path = toml_path. canonicalize ( ) . unwrap ( ) ;
1613
+ toml_path. clone ( )
1552
1614
} else {
1553
1615
toml_path. clone ( )
1554
1616
} ) ;
@@ -1576,6 +1638,26 @@ impl Config {
1576
1638
toml. profile = Some ( "dist" . into ( ) ) ;
1577
1639
}
1578
1640
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
+
1579
1661
if let Some ( include) = & toml. profile {
1580
1662
// Allows creating alias for profile names, allowing
1581
1663
// profiles to be renamed while maintaining back compatibility
@@ -1597,7 +1679,12 @@ impl Config {
1597
1679
) ;
1598
1680
exit ! ( 2 ) ;
1599
1681
} ) ;
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
+ ) ;
1601
1688
}
1602
1689
1603
1690
let mut override_toml = TomlConfig :: default ( ) ;
@@ -1608,7 +1695,12 @@ impl Config {
1608
1695
1609
1696
let mut err = match get_table ( option) {
1610
1697
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
+ ) ;
1612
1704
continue ;
1613
1705
}
1614
1706
Err ( e) => e,
@@ -1619,7 +1711,12 @@ impl Config {
1619
1711
if !value. contains ( '"' ) {
1620
1712
match get_table ( & format ! ( r#"{key}="{value}""# ) ) {
1621
1713
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
+ ) ;
1623
1720
continue ;
1624
1721
}
1625
1722
Err ( e) => err = e,
@@ -1629,7 +1726,7 @@ impl Config {
1629
1726
eprintln ! ( "failed to parse override `{option}`: `{err}" ) ;
1630
1727
exit ! ( 2 )
1631
1728
}
1632
- toml. merge ( override_toml, ReplaceOpt :: Override ) ;
1729
+ toml. merge ( None , & mut Default :: default ( ) , override_toml, ReplaceOpt :: Override ) ;
1633
1730
1634
1731
config. change_id = toml. change_id . inner ;
1635
1732
0 commit comments