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 line_ends = toml_str. lines ( ) . fold ( vec ! [ ] , |mut acc, l| {
63+ acc. push ( acc. last ( ) . copied ( ) . unwrap_or ( 0_usize ) + l. len ( ) + 1 ) ;
64+ acc
65+ } ) ;
66+
67+ Some ( CargoConfigFileReader { toml_str, table : toml, line_ends } )
68+ }
69+
70+ pub ( crate ) fn get_spanned (
71+ & self ,
72+ accessor : impl IntoIterator < Item = & ' a str > ,
73+ ) -> Option < & Spanned < DeValue < ' a > > > {
74+ let mut keys = accessor. into_iter ( ) ;
75+ let mut val = self . table . get_ref ( ) . get ( keys. next ( ) ?) ?;
76+ for key in keys {
77+ let DeValue :: Table ( map) = val. get_ref ( ) else { return None } ;
78+ val = map. get ( key) ?;
79+ }
80+ Some ( val)
81+ }
82+
83+ pub ( crate ) fn get ( & self , accessor : impl IntoIterator < Item = & ' a str > ) -> Option < & DeValue < ' a > > {
84+ self . get_spanned ( accessor) . map ( |it| it. as_ref ( ) )
85+ }
86+
87+ pub ( crate ) fn get_origin_root ( & self , spanned : & Spanned < DeValue < ' a > > ) -> Option < & AbsPath > {
88+ let span = spanned. span ( ) ;
89+
90+ for & line_end in & self . line_ends {
91+ if line_end < span. end {
92+ continue ;
93+ }
94+
95+ let after_span = & self . toml_str [ span. end ..line_end] ;
96+
97+ // table.key = "value" # /parent/.cargo/config.toml
98+ // | |
99+ // span.end line_end
100+ let origin_path = after_span
101+ . strip_prefix ( [ ',' ] ) // strip trailing comma
102+ . unwrap_or ( after_span)
103+ . trim_start ( )
104+ . strip_prefix ( [ '#' ] )
105+ . and_then ( |path| {
106+ let path = path. trim ( ) ;
107+ if path. starts_with ( "environment variable" )
108+ || path. starts_with ( "--config cli option" )
109+ {
110+ None
111+ } else {
112+ Some ( path)
113+ }
114+ } ) ;
115+
116+ return origin_path. and_then ( |path| {
117+ <& Utf8Path >:: from ( path)
118+ . try_into ( )
119+ . ok ( )
120+ // Two levels up to the config file.
121+ // See https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths
122+ . and_then ( AbsPath :: parent)
123+ . and_then ( AbsPath :: parent)
124+ } ) ;
125+ }
126+
127+ None
128+ }
35129}
36130
37131pub ( crate ) fn make_lockfile_copy (
@@ -54,3 +148,74 @@ pub(crate) fn make_lockfile_copy(
54148 }
55149 }
56150}
151+
152+ #[ test]
153+ fn cargo_config_file_reader_works ( ) {
154+ #[ cfg( target_os = "windows" ) ]
155+ let root = "C://ROOT" ;
156+
157+ #[ cfg( not( target_os = "windows" ) ) ]
158+ let root = "/ROOT" ;
159+
160+ let toml = format ! (
161+ r##"
162+ alias.foo = "abc"
163+ alias.bar = "🙂" # {root}/home/.cargo/config.toml
164+ alias.sub-example = [
165+ "sub", # {root}/foo/.cargo/config.toml
166+ "example", # {root}/❤️💛💙/💝/.cargo/config.toml
167+ ]
168+ build.rustflags = [
169+ "--flag", # {root}/home/.cargo/config.toml
170+ "env", # environment variable `CARGO_BUILD_RUSTFLAGS`
171+ "cli", # --config cli option
172+ ]
173+ env.CARGO_WORKSPACE_DIR.relative = true # {root}/home/.cargo/config.toml
174+ env.CARGO_WORKSPACE_DIR.value = "" # {root}/home/.cargo/config.toml
175+ "##
176+ ) ;
177+
178+ let reader = CargoConfigFileReader :: new ( & toml) . unwrap ( ) ;
179+
180+ let alias_foo = reader. get_spanned ( [ "alias" , "foo" ] ) . unwrap ( ) ;
181+ assert_eq ! ( alias_foo. as_ref( ) . as_str( ) . unwrap( ) , "abc" ) ;
182+ assert ! ( reader. get_origin_root( alias_foo) . is_none( ) ) ;
183+
184+ let alias_bar = reader. get_spanned ( [ "alias" , "bar" ] ) . unwrap ( ) ;
185+ assert_eq ! ( alias_bar. as_ref( ) . as_str( ) . unwrap( ) , "🙂" ) ;
186+ assert_eq ! ( reader. get_origin_root( alias_bar) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
187+
188+ let alias_sub_example = reader. get_spanned ( [ "alias" , "sub-example" ] ) . unwrap ( ) ;
189+ assert ! ( reader. get_origin_root( alias_sub_example) . is_none( ) ) ;
190+ let alias_sub_example = alias_sub_example. as_ref ( ) . as_array ( ) . unwrap ( ) ;
191+
192+ assert_eq ! ( alias_sub_example[ 0 ] . get_ref( ) . as_str( ) . unwrap( ) , "sub" ) ;
193+ assert_eq ! (
194+ reader. get_origin_root( & alias_sub_example[ 0 ] ) . unwrap( ) . as_str( ) ,
195+ format!( "{root}/foo" )
196+ ) ;
197+
198+ assert_eq ! ( alias_sub_example[ 1 ] . get_ref( ) . as_str( ) . unwrap( ) , "example" ) ;
199+ assert_eq ! (
200+ reader. get_origin_root( & alias_sub_example[ 1 ] ) . unwrap( ) . as_str( ) ,
201+ format!( "{root}/❤️💛💙/💝" )
202+ ) ;
203+
204+ let build_rustflags = reader. get ( [ "build" , "rustflags" ] ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
205+ assert_eq ! (
206+ reader. get_origin_root( & build_rustflags[ 0 ] ) . unwrap( ) . as_str( ) ,
207+ format!( "{root}/home" )
208+ ) ;
209+ assert ! ( reader. get_origin_root( & build_rustflags[ 1 ] ) . is_none( ) ) ;
210+ assert ! ( reader. get_origin_root( & build_rustflags[ 2 ] ) . is_none( ) ) ;
211+
212+ let env_cargo_workspace_dir =
213+ reader. get ( [ "env" , "CARGO_WORKSPACE_DIR" ] ) . unwrap ( ) . as_table ( ) . unwrap ( ) ;
214+ let env_relative = & env_cargo_workspace_dir[ "relative" ] ;
215+ assert ! ( env_relative. as_ref( ) . as_bool( ) . unwrap( ) ) ;
216+ assert_eq ! ( reader. get_origin_root( env_relative) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
217+
218+ let env_val = & env_cargo_workspace_dir[ "value" ] ;
219+ assert_eq ! ( env_val. as_ref( ) . as_str( ) . unwrap( ) , "" ) ;
220+ assert_eq ! ( reader. get_origin_root( env_val) . unwrap( ) . as_str( ) , format!( "{root}/home" ) ) ;
221+ }
0 commit comments