66use std:: cell:: { Cell , RefCell } ;
77use std:: collections:: { BTreeSet , HashMap , HashSet } ;
88use std:: fmt:: { self , Display } ;
9+ use std:: hash:: Hash ;
910use std:: io:: IsTerminal ;
1011use std:: path:: { Path , PathBuf , absolute} ;
1112use 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
749751trait 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
753761impl 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
905958impl < 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