@@ -79,18 +79,31 @@ pub(crate) fn cargo_config_env(
7979 for ( key, entry) in env_toml {
8080 let key = key. as_ref ( ) . as_ref ( ) ;
8181 let value = match entry. as_ref ( ) {
82- DeValue :: String ( s) => String :: from ( s. clone ( ) ) ,
82+ DeValue :: String ( s) => {
83+ // Plain string entries have no `force` option, so they should not
84+ // override existing environment variables (matching Cargo behavior).
85+ if extra_env. get ( key) . is_some_and ( Option :: is_some) {
86+ continue ;
87+ }
88+ if let Ok ( val) = std:: env:: var ( key) { val } else { String :: from ( s. clone ( ) ) }
89+ }
8390 DeValue :: Table ( entry) => {
8491 // Each entry MUST have a `value` key.
8592 let Some ( map) = entry. get ( "value" ) . and_then ( |v| v. as_ref ( ) . as_str ( ) ) else {
8693 continue ;
8794 } ;
88- // If the entry already exists in the environment AND the `force` key is not set to
89- // true, then don't overwrite the value.
90- if extra_env. get ( key) . is_some_and ( Option :: is_some)
91- && !entry. get ( "force" ) . and_then ( |v| v. as_ref ( ) . as_bool ( ) ) . unwrap_or ( false )
92- {
93- continue ;
95+ let is_forced =
96+ entry. get ( "force" ) . and_then ( |v| v. as_ref ( ) . as_bool ( ) ) . unwrap_or ( false ) ;
97+ // If the entry already exists in the environment AND the `force` key is not set
98+ // to true, use the existing value instead of the config value.
99+ if !is_forced {
100+ if extra_env. get ( key) . is_some_and ( Option :: is_some) {
101+ continue ;
102+ }
103+ if let Ok ( val) = std:: env:: var ( key) {
104+ env. insert ( key, val) ;
105+ continue ;
106+ }
94107 }
95108
96109 if let Some ( base) = entry. get ( "relative" ) . and_then ( |v| {
@@ -124,38 +137,80 @@ fn parse_output_cargo_config_env_works() {
124137 . unwrap ( ) ;
125138 let config_path = cwd. join ( ".cargo" ) . join ( "config.toml" ) ;
126139 let raw = r#"
127- env.CARGO_WORKSPACE_DIR .relative = true
128- env.CARGO_WORKSPACE_DIR .value = ""
129- env.INVALID .relative = "invalidbool"
130- env.INVALID .value = "../relative"
131- env.RELATIVE .relative = true
132- env.RELATIVE .value = "../relative"
133- env.TEST .value = "test"
134- env.FORCED .value = "test"
135- env.FORCED .force = true
136- env.UNFORCED .value = "test"
137- env.UNFORCED .forced = false
138- env.OVERWRITTEN .value = "test"
139- env.NOT_AN_OBJECT = "value"
140+ env.RA_TEST_WORKSPACE_DIR .relative = true
141+ env.RA_TEST_WORKSPACE_DIR .value = ""
142+ env.RA_TEST_INVALID .relative = "invalidbool"
143+ env.RA_TEST_INVALID .value = "../relative"
144+ env.RA_TEST_RELATIVE .relative = true
145+ env.RA_TEST_RELATIVE .value = "../relative"
146+ env.RA_TEST_UNSET .value = "test"
147+ env.RA_TEST_FORCED .value = "test"
148+ env.RA_TEST_FORCED .force = true
149+ env.RA_TEST_UNFORCED .value = "test"
150+ env.RA_TEST_UNFORCED .forced = false
151+ env.RA_TEST_OVERWRITTEN .value = "test"
152+ env.RA_TEST_NOT_AN_OBJECT = "value"
140153"# ;
141154 let raw = raw. lines ( ) . map ( |l| format ! ( "{l} # {config_path}" ) ) . join ( "\n " ) ;
142155 let config = CargoConfigFile :: from_string_for_test ( raw) ;
143156 let extra_env = [
144- ( "FORCED " , Some ( "ignored" ) ) ,
145- ( "UNFORCED " , Some ( "newvalue" ) ) ,
146- ( "OVERWRITTEN " , Some ( "newvalue" ) ) ,
147- ( "TEST " , None ) ,
157+ ( "RA_TEST_FORCED " , Some ( "ignored" ) ) ,
158+ ( "RA_TEST_UNFORCED " , Some ( "newvalue" ) ) ,
159+ ( "RA_TEST_OVERWRITTEN " , Some ( "newvalue" ) ) ,
160+ ( "RA_TEST_UNSET " , None ) ,
148161 ]
149162 . iter ( )
150163 . map ( |( k, v) | ( k. to_string ( ) , v. map ( ToString :: to_string) ) )
151164 . collect ( ) ;
152165 let env = cargo_config_env ( & Some ( config) , & extra_env) ;
153- assert_eq ! ( env. get( "CARGO_WORKSPACE_DIR" ) . as_deref( ) , Some ( cwd. join( "" ) . as_str( ) ) ) ;
154- assert_eq ! ( env. get( "RELATIVE" ) . as_deref( ) , Some ( cwd. join( "../relative" ) . as_str( ) ) ) ;
155- assert_eq ! ( env. get( "INVALID" ) . as_deref( ) , Some ( "../relative" ) ) ;
156- assert_eq ! ( env. get( "TEST" ) . as_deref( ) , Some ( "test" ) ) ;
157- assert_eq ! ( env. get( "FORCED" ) . as_deref( ) , Some ( "test" ) ) ;
158- assert_eq ! ( env. get( "UNFORCED" ) . as_deref( ) , Some ( "newvalue" ) ) ;
159- assert_eq ! ( env. get( "OVERWRITTEN" ) . as_deref( ) , Some ( "newvalue" ) ) ;
160- assert_eq ! ( env. get( "NOT_AN_OBJECT" ) . as_deref( ) , Some ( "value" ) ) ;
166+ assert_eq ! ( env. get( "RA_TEST_WORKSPACE_DIR" ) . as_deref( ) , Some ( cwd. join( "" ) . as_str( ) ) ) ;
167+ assert_eq ! ( env. get( "RA_TEST_RELATIVE" ) . as_deref( ) , Some ( cwd. join( "../relative" ) . as_str( ) ) ) ;
168+ assert_eq ! ( env. get( "RA_TEST_INVALID" ) . as_deref( ) , Some ( "../relative" ) ) ;
169+ assert_eq ! ( env. get( "RA_TEST_UNSET" ) . as_deref( ) , Some ( "test" ) ) ;
170+ assert_eq ! ( env. get( "RA_TEST_FORCED" ) . as_deref( ) , Some ( "test" ) ) ;
171+ assert_eq ! ( env. get( "RA_TEST_UNFORCED" ) . as_deref( ) , Some ( "newvalue" ) ) ;
172+ assert_eq ! ( env. get( "RA_TEST_OVERWRITTEN" ) . as_deref( ) , Some ( "newvalue" ) ) ;
173+ assert_eq ! ( env. get( "RA_TEST_NOT_AN_OBJECT" ) . as_deref( ) , Some ( "value" ) ) ;
174+ }
175+
176+ #[ test]
177+ fn cargo_config_env_respects_process_env ( ) {
178+ use itertools:: Itertools ;
179+
180+ let cwd = paths:: AbsPathBuf :: try_from (
181+ paths:: Utf8PathBuf :: try_from ( std:: env:: current_dir ( ) . unwrap ( ) ) . unwrap ( ) ,
182+ )
183+ . unwrap ( ) ;
184+ let config_path = cwd. join ( ".cargo" ) . join ( "config.toml" ) ;
185+
186+ // SAFETY: this test is not run in parallel with other tests that depend on these env vars.
187+ unsafe {
188+ std:: env:: set_var ( "RA_TEST_PROCESS_ENV_STRING" , "from_process" ) ;
189+ std:: env:: set_var ( "RA_TEST_PROCESS_ENV_TABLE" , "from_process" ) ;
190+ std:: env:: set_var ( "RA_TEST_PROCESS_ENV_FORCED" , "from_process" ) ;
191+ }
192+
193+ let raw = r#"
194+ env.RA_TEST_PROCESS_ENV_STRING = "from_config"
195+ env.RA_TEST_PROCESS_ENV_TABLE.value = "from_config"
196+ env.RA_TEST_PROCESS_ENV_FORCED.value = "from_config"
197+ env.RA_TEST_PROCESS_ENV_FORCED.force = true
198+ "# ;
199+ let raw = raw. lines ( ) . map ( |l| format ! ( "{l} # {config_path}" ) ) . join ( "\n " ) ;
200+ let config = CargoConfigFile :: from_string_for_test ( raw) ;
201+ let extra_env = FxHashMap :: default ( ) ;
202+ let env = cargo_config_env ( & Some ( config) , & extra_env) ;
203+
204+ // Plain string form should use process env value, not config value
205+ assert_eq ! ( env. get( "RA_TEST_PROCESS_ENV_STRING" ) . as_deref( ) , Some ( "from_process" ) ) ;
206+ // Table form without force should use process env value, not config value
207+ assert_eq ! ( env. get( "RA_TEST_PROCESS_ENV_TABLE" ) . as_deref( ) , Some ( "from_process" ) ) ;
208+ // Table form with force=true should override process env
209+ assert_eq ! ( env. get( "RA_TEST_PROCESS_ENV_FORCED" ) . as_deref( ) , Some ( "from_config" ) ) ;
210+
211+ unsafe {
212+ std:: env:: remove_var ( "RA_TEST_PROCESS_ENV_STRING" ) ;
213+ std:: env:: remove_var ( "RA_TEST_PROCESS_ENV_TABLE" ) ;
214+ std:: env:: remove_var ( "RA_TEST_PROCESS_ENV_FORCED" ) ;
215+ }
161216}
0 commit comments