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 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,59 @@ pub(crate) fn make_lockfile_copy(
54148 }
55149 }
56150}
151+
152+ #[ test]
153+ fn cargo_config_file_reader_works ( ) {
154+ let toml = r##"
155+ alias.foo = "abc"
156+ alias.bar = "🙂" # /ROOT/home/.cargo/config.toml
157+ alias.sub-example = [
158+ "sub", # /ROOT/foo/.cargo/config.toml
159+ "example", # /ROOT/bar/.cargo/config.toml
160+ ]
161+ build.rustflags = [
162+ "--flag", # /ROOT/home/.cargo/config.toml
163+ "env", # environment variable `CARGO_BUILD_RUSTFLAGS`
164+ "cli", # --config cli option
165+ ]
166+ env.CARGO_WORKSPACE_DIR.relative = true # /ROOT/home/.cargo/config.toml
167+ env.CARGO_WORKSPACE_DIR.value = "" # /ROOT/home/.cargo/config.toml
168+ "## ;
169+ #[ cfg( target_os = "windows" ) ]
170+ let toml = & toml. replace ( "/ROOT" , "C:/" ) ;
171+
172+ let reader = CargoConfigFileReader :: new ( toml) . unwrap ( ) ;
173+
174+ let alias_foo = reader. get_spanned ( [ "alias" , "foo" ] ) . unwrap ( ) ;
175+ assert_eq ! ( alias_foo. as_ref( ) . as_str( ) . unwrap( ) , "abc" ) ;
176+ assert ! ( reader. get_origin_root( alias_foo) . is_none( ) ) ;
177+
178+ let alias_bar = reader. get_spanned ( [ "alias" , "bar" ] ) . unwrap ( ) ;
179+ assert_eq ! ( alias_bar. as_ref( ) . as_str( ) . unwrap( ) , "🙂" ) ;
180+ assert_eq ! ( reader. get_origin_root( alias_bar) . unwrap( ) . as_str( ) , "/ROOT/home" ) ;
181+
182+ let alias_sub_example = reader. get_spanned ( [ "alias" , "sub-example" ] ) . unwrap ( ) ;
183+ assert ! ( reader. get_origin_root( alias_sub_example) . is_none( ) ) ;
184+ let alias_sub_example = alias_sub_example. as_ref ( ) . as_array ( ) . unwrap ( ) ;
185+
186+ assert_eq ! ( alias_sub_example[ 0 ] . get_ref( ) . as_str( ) . unwrap( ) , "sub" ) ;
187+ assert_eq ! ( reader. get_origin_root( & alias_sub_example[ 0 ] ) . unwrap( ) . as_str( ) , "/ROOT/foo" ) ;
188+
189+ assert_eq ! ( alias_sub_example[ 1 ] . get_ref( ) . as_str( ) . unwrap( ) , "example" ) ;
190+ assert_eq ! ( reader. get_origin_root( & alias_sub_example[ 1 ] ) . unwrap( ) . as_str( ) , "/ROOT/bar" ) ;
191+
192+ let build_rustflags = reader. get ( [ "build" , "rustflags" ] ) . unwrap ( ) . as_array ( ) . unwrap ( ) ;
193+ assert_eq ! ( reader. get_origin_root( & build_rustflags[ 0 ] ) . unwrap( ) . as_str( ) , "/ROOT/home" ) ;
194+ assert ! ( reader. get_origin_root( & build_rustflags[ 1 ] ) . is_none( ) ) ;
195+ assert ! ( reader. get_origin_root( & build_rustflags[ 2 ] ) . is_none( ) ) ;
196+
197+ let env_cargo_workspace_dir =
198+ reader. get ( [ "env" , "CARGO_WORKSPACE_DIR" ] ) . unwrap ( ) . as_table ( ) . unwrap ( ) ;
199+ let env_relative = & env_cargo_workspace_dir[ "relative" ] ;
200+ assert ! ( env_relative. as_ref( ) . as_bool( ) . unwrap( ) ) ;
201+ assert_eq ! ( reader. get_origin_root( env_relative) . unwrap( ) . as_str( ) , "/ROOT/home" ) ;
202+
203+ let env_val = & env_cargo_workspace_dir[ "value" ] ;
204+ assert_eq ! ( env_val. as_ref( ) . as_str( ) . unwrap( ) , "" ) ;
205+ assert_eq ! ( reader. get_origin_root( env_val) . unwrap( ) . as_str( ) , "/ROOT/home" ) ;
206+ }
0 commit comments