@@ -748,6 +748,7 @@ enum ReplaceOpt {
748748trait Merge {
749749 fn merge (
750750 & mut self ,
751+ parent_config_path : Option < PathBuf > ,
751752 included_extensions : & mut HashSet < PathBuf > ,
752753 other : Self ,
753754 replace : ReplaceOpt ,
@@ -757,26 +758,35 @@ trait Merge {
757758impl Merge for TomlConfig {
758759 fn merge (
759760 & mut self ,
761+ parent_config_path : Option < PathBuf > ,
760762 included_extensions : & mut HashSet < PathBuf > ,
761763 TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include } : Self ,
762764 replace : ReplaceOpt ,
763765 ) {
764766 fn do_merge < T : Merge > ( x : & mut Option < T > , y : Option < T > , replace : ReplaceOpt ) {
765767 if let Some ( new) = y {
766768 if let Some ( original) = x {
767- original. merge ( & mut Default :: default ( ) , new, replace) ;
769+ original. merge ( None , & mut Default :: default ( ) , new, replace) ;
768770 } else {
769771 * x = Some ( new) ;
770772 }
771773 }
772774 }
773775
774- for include_path in include. clone ( ) . unwrap_or_default ( ) {
776+ let parent_dir = parent_config_path
777+ . as_ref ( )
778+ . and_then ( |p| p. parent ( ) . map ( ToOwned :: to_owned) )
779+ . unwrap_or_default ( ) ;
780+
781+ for include_path in include. clone ( ) . unwrap_or_default ( ) . iter ( ) . rev ( ) {
782+ let include_path = parent_dir. join ( include_path) ;
783+ let include_path = include_path. canonicalize ( ) . unwrap_or_else ( |e| {
784+ eprintln ! ( "ERROR: Failed to canonicalize '{}' path: {e}" , include_path. display( ) ) ;
785+ exit ! ( 2 ) ;
786+ } ) ;
787+
775788 let included_toml = Config :: get_toml ( & include_path) . unwrap_or_else ( |e| {
776- eprintln ! (
777- "ERROR: Failed to parse default config profile at '{}': {e}" ,
778- include_path. display( )
779- ) ;
789+ eprintln ! ( "ERROR: Failed to parse '{}': {e}" , include_path. display( ) ) ;
780790 exit ! ( 2 ) ;
781791 } ) ;
782792
@@ -786,13 +796,20 @@ impl Merge for TomlConfig {
786796 include_path. display( )
787797 ) ;
788798
789- self . merge ( included_extensions, included_toml, ReplaceOpt :: Override ) ;
799+ self . merge (
800+ Some ( include_path. clone ( ) ) ,
801+ included_extensions,
802+ included_toml,
803+ // Ensures that parent configuration always takes precedence
804+ // over child configurations.
805+ ReplaceOpt :: IgnoreDuplicate ,
806+ ) ;
790807
791808 included_extensions. remove ( & include_path) ;
792809 }
793810
794- self . change_id . inner . merge ( & mut Default :: default ( ) , change_id. inner , replace) ;
795- self . profile . merge ( & mut Default :: default ( ) , profile, replace) ;
811+ self . change_id . inner . merge ( None , & mut Default :: default ( ) , change_id. inner , replace) ;
812+ self . profile . merge ( None , & mut Default :: default ( ) , profile, replace) ;
796813
797814 do_merge ( & mut self . build , build, replace) ;
798815 do_merge ( & mut self . install , install, replace) ;
@@ -807,7 +824,7 @@ impl Merge for TomlConfig {
807824 ( Some ( original_target) , Some ( new_target) ) => {
808825 for ( triple, new) in new_target {
809826 if let Some ( original) = original_target. get_mut ( & triple) {
810- original. merge ( & mut Default :: default ( ) , new, replace) ;
827+ original. merge ( None , & mut Default :: default ( ) , new, replace) ;
811828 } else {
812829 original_target. insert ( triple, new) ;
813830 }
@@ -828,7 +845,13 @@ macro_rules! define_config {
828845 }
829846
830847 impl Merge for $name {
831- fn merge( & mut self , _included_extensions: & mut HashSet <PathBuf >, other: Self , replace: ReplaceOpt ) {
848+ fn merge(
849+ & mut self ,
850+ _parent_config_path: Option <PathBuf >,
851+ _included_extensions: & mut HashSet <PathBuf >,
852+ other: Self ,
853+ replace: ReplaceOpt
854+ ) {
832855 $(
833856 match replace {
834857 ReplaceOpt :: IgnoreDuplicate => {
@@ -930,6 +953,7 @@ macro_rules! define_config {
930953impl < T > Merge for Option < T > {
931954 fn merge (
932955 & mut self ,
956+ _parent_config_path : Option < PathBuf > ,
933957 _included_extensions : & mut HashSet < PathBuf > ,
934958 other : Self ,
935959 replace : ReplaceOpt ,
@@ -1580,7 +1604,8 @@ impl Config {
15801604 // but not if `bootstrap.toml` hasn't been created.
15811605 let mut toml = if !using_default_path || toml_path. exists ( ) {
15821606 config. config = Some ( if cfg ! ( not( test) ) {
1583- toml_path. canonicalize ( ) . unwrap ( )
1607+ toml_path = toml_path. canonicalize ( ) . unwrap ( ) ;
1608+ toml_path. clone ( )
15841609 } else {
15851610 toml_path. clone ( )
15861611 } ) ;
@@ -1608,6 +1633,24 @@ impl Config {
16081633 toml. profile = Some ( "dist" . into ( ) ) ;
16091634 }
16101635
1636+ // Reverse the list to ensure the last added config extension remains the most dominant.
1637+ // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml".
1638+ //
1639+ // This must be handled before applying the `profile` since `include`s should always take
1640+ // precedence over `profile`s.
1641+ for include_path in toml. include . clone ( ) . unwrap_or_default ( ) . iter ( ) . rev ( ) {
1642+ let included_toml = get_toml ( include_path) . unwrap_or_else ( |e| {
1643+ eprintln ! ( "ERROR: Failed to parse '{}': {e}" , include_path. display( ) ) ;
1644+ exit ! ( 2 ) ;
1645+ } ) ;
1646+ toml. merge (
1647+ Some ( toml_path. join ( include_path) ) ,
1648+ & mut Default :: default ( ) ,
1649+ included_toml,
1650+ ReplaceOpt :: IgnoreDuplicate ,
1651+ ) ;
1652+ }
1653+
16111654 if let Some ( include) = & toml. profile {
16121655 // Allows creating alias for profile names, allowing
16131656 // profiles to be renamed while maintaining back compatibility
@@ -1629,18 +1672,12 @@ impl Config {
16291672 ) ;
16301673 exit ! ( 2 ) ;
16311674 } ) ;
1632- toml. merge ( & mut Default :: default ( ) , included_toml, ReplaceOpt :: IgnoreDuplicate ) ;
1633- }
1634-
1635- for include_path in toml. include . clone ( ) . unwrap_or_default ( ) {
1636- let included_toml = get_toml ( & include_path) . unwrap_or_else ( |e| {
1637- eprintln ! (
1638- "ERROR: Failed to parse default config profile at '{}': {e}" ,
1639- include_path. display( )
1640- ) ;
1641- exit ! ( 2 ) ;
1642- } ) ;
1643- toml. merge ( & mut Default :: default ( ) , included_toml, ReplaceOpt :: Override ) ;
1675+ toml. merge (
1676+ Some ( include_path) ,
1677+ & mut Default :: default ( ) ,
1678+ included_toml,
1679+ ReplaceOpt :: IgnoreDuplicate ,
1680+ ) ;
16441681 }
16451682
16461683 let mut override_toml = TomlConfig :: default ( ) ;
@@ -1651,7 +1688,12 @@ impl Config {
16511688
16521689 let mut err = match get_table ( option) {
16531690 Ok ( v) => {
1654- override_toml. merge ( & mut Default :: default ( ) , v, ReplaceOpt :: ErrorOnDuplicate ) ;
1691+ override_toml. merge (
1692+ None ,
1693+ & mut Default :: default ( ) ,
1694+ v,
1695+ ReplaceOpt :: ErrorOnDuplicate ,
1696+ ) ;
16551697 continue ;
16561698 }
16571699 Err ( e) => e,
@@ -1663,6 +1705,7 @@ impl Config {
16631705 match get_table ( & format ! ( r#"{key}="{value}""# ) ) {
16641706 Ok ( v) => {
16651707 override_toml. merge (
1708+ None ,
16661709 & mut Default :: default ( ) ,
16671710 v,
16681711 ReplaceOpt :: ErrorOnDuplicate ,
@@ -1676,7 +1719,7 @@ impl Config {
16761719 eprintln ! ( "failed to parse override `{option}`: `{err}" ) ;
16771720 exit ! ( 2 )
16781721 }
1679- toml. merge ( & mut Default :: default ( ) , override_toml, ReplaceOpt :: Override ) ;
1722+ toml. merge ( None , & mut Default :: default ( ) , override_toml, ReplaceOpt :: Override ) ;
16801723
16811724 config. change_id = toml. change_id . inner ;
16821725
0 commit comments