1- //! Read `.cargo/config.toml` as a JSON object
2- use paths:: { Utf8Path , Utf8PathBuf } ;
1+ //! Read `.cargo/config.toml` as a TOML table
2+ use paths:: { AbsPath , Utf8Path , Utf8PathBuf } ;
33use rustc_hash:: FxHashMap ;
4+ use toml:: {
5+ Spanned ,
6+ de:: { DeTable , DeValue } ,
7+ } ;
48use toolchain:: Tool ;
59
610use crate :: { ManifestPath , Sysroot , utf8_stdout} ;
711
8- pub ( crate ) type CargoConfigFile = serde_json:: Map < String , serde_json:: Value > ;
9-
10- pub ( crate ) fn read (
11- manifest : & ManifestPath ,
12- extra_env : & FxHashMap < String , Option < String > > ,
13- sysroot : & Sysroot ,
14- ) -> Option < CargoConfigFile > {
15- let mut cargo_config = sysroot. tool ( Tool :: Cargo , manifest. parent ( ) , extra_env) ;
16- cargo_config
17- . args ( [ "-Z" , "unstable-options" , "config" , "get" , "--format" , "json" ] )
18- . env ( "RUSTC_BOOTSTRAP" , "1" ) ;
19- if manifest. is_rust_manifest ( ) {
20- cargo_config. arg ( "-Zscript" ) ;
21- }
22-
23- tracing:: debug!( "Discovering cargo config by {:?}" , cargo_config) ;
24- let json: serde_json:: Map < String , serde_json:: Value > = utf8_stdout ( & mut cargo_config)
25- . inspect ( |json| {
26- tracing:: debug!( "Discovered cargo config: {:?}" , json) ;
27- } )
28- . inspect_err ( |err| {
29- tracing:: debug!( "Failed to discover cargo config: {:?}" , err) ;
30- } )
31- . ok ( )
32- . and_then ( |stdout| serde_json:: from_str ( & stdout) . ok ( ) ) ?;
33-
34- Some ( json)
12+ #[ derive( Clone ) ]
13+ pub struct CargoConfigFile ( String ) ;
14+
15+ impl CargoConfigFile {
16+ pub ( crate ) fn load (
17+ manifest : & ManifestPath ,
18+ extra_env : & FxHashMap < String , Option < String > > ,
19+ sysroot : & Sysroot ,
20+ ) -> Option < Self > {
21+ let mut cargo_config = sysroot. tool ( Tool :: Cargo , manifest. parent ( ) , extra_env) ;
22+ cargo_config
23+ . args ( [ "-Z" , "unstable-options" , "config" , "get" , "--format" , "toml" , "--show-origin" ] )
24+ . env ( "RUSTC_BOOTSTRAP" , "1" ) ;
25+ if manifest. is_rust_manifest ( ) {
26+ cargo_config. arg ( "-Zscript" ) ;
27+ }
28+
29+ tracing:: debug!( "Discovering cargo config by {cargo_config:?}" ) ;
30+ utf8_stdout ( & mut cargo_config)
31+ . inspect ( |toml| {
32+ tracing:: debug!( "Discovered cargo config: {toml:?}" ) ;
33+ } )
34+ . inspect_err ( |err| {
35+ tracing:: debug!( "Failed to discover cargo config: {err:?}" ) ;
36+ } )
37+ . ok ( )
38+ . map ( CargoConfigFile )
39+ }
40+
41+ pub ( crate ) fn read < ' a > ( & ' a self ) -> Option < CargoConfigFileReader < ' a > > {
42+ CargoConfigFileReader :: new ( & self . 0 )
43+ }
44+
45+ #[ cfg( test) ]
46+ pub ( crate ) fn from_string_for_test ( s : String ) -> Self {
47+ CargoConfigFile ( s)
48+ }
49+ }
50+
51+ pub ( crate ) struct CargoConfigFileReader < ' a > {
52+ toml_str : & ' a str ,
53+ line_ends : Vec < usize > ,
54+ table : Spanned < DeTable < ' a > > ,
55+ }
56+
57+ impl < ' a > CargoConfigFileReader < ' a > {
58+ fn new ( toml_str : & ' a str ) -> Option < Self > {
59+ let toml = DeTable :: parse ( toml_str)
60+ . inspect_err ( |err| tracing:: debug!( "Failed to parse cargo config into toml: {err:?}" ) )
61+ . ok ( ) ?;
62+ let mut last_line_end = 0 ;
63+ let line_ends = toml_str
64+ . lines ( )
65+ . map ( |l| {
66+ last_line_end += l. len ( ) + 1 ;
67+ last_line_end
68+ } )
69+ . collect ( ) ;
70+
71+ Some ( CargoConfigFileReader { toml_str, table : toml, line_ends } )
72+ }
73+
74+ pub ( crate ) fn get_spanned (
75+ & self ,
76+ accessor : impl IntoIterator < Item = & ' a str > ,
77+ ) -> Option < & Spanned < DeValue < ' a > > > {
78+ let mut keys = accessor. into_iter ( ) ;
79+ let mut val = self . table . get_ref ( ) . get ( keys. next ( ) ?) ?;
80+ for key in keys {
81+ let DeValue :: Table ( map) = val. get_ref ( ) else { return None } ;
82+ val = map. get ( key) ?;
83+ }
84+ Some ( val)
85+ }
86+
87+ pub ( crate ) fn get ( & self , accessor : impl IntoIterator < Item = & ' a str > ) -> Option < & DeValue < ' a > > {
88+ self . get_spanned ( accessor) . map ( |it| it. as_ref ( ) )
89+ }
90+
91+ pub ( crate ) fn get_origin_root ( & self , spanned : & Spanned < DeValue < ' a > > ) -> Option < & AbsPath > {
92+ let span = spanned. span ( ) ;
93+
94+ for & line_end in & self . line_ends {
95+ if line_end < span. end {
96+ continue ;
97+ }
98+
99+ let after_span = & self . toml_str [ span. end ..line_end] ;
100+
101+ // table.key = "value" # /parent/.cargo/config.toml
102+ // | |
103+ // span.end line_end
104+ let origin_path = after_span
105+ . strip_prefix ( [ ',' ] ) // strip trailing comma
106+ . unwrap_or ( after_span)
107+ . trim_start ( )
108+ . strip_prefix ( [ '#' ] )
109+ . and_then ( |path| {
110+ let path = path. trim ( ) ;
111+ if path. starts_with ( "environment variable" )
112+ || path. starts_with ( "--config cli option" )
113+ {
114+ None
115+ } else {
116+ Some ( path)
117+ }
118+ } ) ;
119+
120+ return origin_path. and_then ( |path| {
121+ <& Utf8Path >:: from ( path)
122+ . try_into ( )
123+ . ok ( )
124+ // Two levels up to the config file.
125+ // See https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths
126+ . and_then ( AbsPath :: parent)
127+ . and_then ( AbsPath :: parent)
128+ } ) ;
129+ }
130+
131+ None
132+ }
35133}
36134
37135pub ( crate ) fn make_lockfile_copy (
@@ -54,3 +152,74 @@ pub(crate) fn make_lockfile_copy(
54152 }
55153 }
56154}
155+
156+ #[ test]
157+ fn cargo_config_file_reader_works ( ) {
158+ #[ cfg( target_os = "windows" ) ]
159+ let root = "C://ROOT" ;
160+
161+ #[ cfg( not( target_os = "windows" ) ) ]
162+ let root = "/ROOT" ;
163+
164+ let toml = format ! (
165+ r##"
166+ alias.foo = "abc"
167+ alias.bar = "🙂" # {root}/home/.cargo/config.toml
168+ alias.sub-example = [
169+ "sub", # {root}/foo/.cargo/config.toml
170+ "example", # {root}/❤️💛💙/💝/.cargo/config.toml
171+ ]
172+ build.rustflags = [
173+ "--flag", # {root}/home/.cargo/config.toml
174+ "env", # environment variable `CARGO_BUILD_RUSTFLAGS`
175+ "cli", # --config cli option
176+ ]
177+ env.CARGO_WORKSPACE_DIR.relative = true # {root}/home/.cargo/config.toml
178+ env.CARGO_WORKSPACE_DIR.value = "" # {root}/home/.cargo/config.toml
179+ "##
180+ ) ;
181+
182+ let reader = CargoConfigFileReader :: new ( & toml) . unwrap ( ) ;
183+
184+ let alias_foo = reader. get_spanned ( [ "alias" , "foo" ] ) . unwrap ( ) ;
185+ assert_eq ! ( alias_foo. as_ref( ) . as_str( ) . unwrap( ) , "abc" ) ;
186+ assert ! ( reader. get_origin_root( alias_foo) . is_none( ) ) ;
187+
188+ let alias_bar = reader. get_spanned ( [ "alias" , "bar" ] ) . unwrap ( ) ;
189+ assert_eq ! ( alias_bar. as_ref( ) . as_str( ) . unwrap( ) , "🙂" ) ;
190+ assert_eq ! ( reader. get_origin_root( alias_bar) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
191+
192+ let alias_sub_example = reader. get_spanned ( [ "alias" , "sub-example" ] ) . unwrap ( ) ;
193+ assert ! ( reader. get_origin_root( alias_sub_example) . is_none( ) ) ;
194+ let alias_sub_example = alias_sub_example. as_ref ( ) . as_array ( ) . unwrap ( ) ;
195+
196+ assert_eq ! ( alias_sub_example[ 0 ] . get_ref( ) . as_str( ) . unwrap( ) , "sub" ) ;
197+ assert_eq ! (
198+ reader. get_origin_root( & alias_sub_example[ 0 ] ) . unwrap( ) . as_str( ) ,
199+ format!( "{root}/foo" )
200+ ) ;
201+
202+ assert_eq ! ( alias_sub_example[ 1 ] . get_ref( ) . as_str( ) . unwrap( ) , "example" ) ;
203+ assert_eq ! (
204+ reader. get_origin_root( & alias_sub_example[ 1 ] ) . unwrap( ) . as_str( ) ,
205+ format!( "{root}/❤️💛💙/💝" )
206+ ) ;
207+
208+ let build_rustflags = reader. get ( [ "build" , "rustflags" ] ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
209+ assert_eq ! (
210+ reader. get_origin_root( & build_rustflags[ 0 ] ) . unwrap( ) . as_str( ) ,
211+ format!( "{root}/home" )
212+ ) ;
213+ assert ! ( reader. get_origin_root( & build_rustflags[ 1 ] ) . is_none( ) ) ;
214+ assert ! ( reader. get_origin_root( & build_rustflags[ 2 ] ) . is_none( ) ) ;
215+
216+ let env_cargo_workspace_dir =
217+ reader. get ( [ "env" , "CARGO_WORKSPACE_DIR" ] ) . unwrap ( ) . as_table ( ) . unwrap ( ) ;
218+ let env_relative = & env_cargo_workspace_dir[ "relative" ] ;
219+ assert ! ( env_relative. as_ref( ) . as_bool( ) . unwrap( ) ) ;
220+ assert_eq ! ( reader. get_origin_root( env_relative) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
221+
222+ let env_val = & env_cargo_workspace_dir[ "value" ] ;
223+ assert_eq ! ( env_val. as_ref( ) . as_str( ) . unwrap( ) , "" ) ;
224+ assert_eq ! ( reader. get_origin_root( env_val) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
225+ }
0 commit comments