@@ -7,9 +7,49 @@ use std::path::Path;
7
7
use std:: thread;
8
8
use toml:: Value as TomlValue ;
9
9
10
+ #[ cfg( all( test, target_os = "macos" ) ) ]
11
+ use std:: sync:: Mutex ;
12
+ #[ cfg( all( test, target_os = "macos" ) ) ]
13
+ use std:: sync:: OnceLock ;
14
+
10
15
const CONFIG_TOML_FILE : & str = "config.toml" ;
11
16
const CONFIG_OVERRIDE_TOML_FILE : & str = "config_override.toml" ;
12
17
18
+ #[ derive( Debug ) ]
19
+ pub ( crate ) struct LoadedConfigLayers {
20
+ pub base : TomlValue ,
21
+ pub override_layer : Option < TomlValue > ,
22
+ pub managed_layer : Option < TomlValue > ,
23
+ }
24
+
25
+ #[ cfg( all( test, target_os = "macos" ) ) ]
26
+ static TEST_MANAGED_PREFERENCES_OVERRIDE : OnceLock < Mutex < Option < String > > > = OnceLock :: new ( ) ;
27
+
28
+ #[ cfg( all( test, target_os = "macos" ) ) ]
29
+ fn test_managed_preferences_override_storage ( ) -> & ' static Mutex < Option < String > > {
30
+ TEST_MANAGED_PREFERENCES_OVERRIDE . get_or_init ( || Mutex :: new ( None ) )
31
+ }
32
+
33
+ #[ cfg( all( test, target_os = "macos" ) ) ]
34
+ fn test_managed_preferences_override ( ) -> Option < String > {
35
+ lock_test_managed_preferences_override_storage ( ) . clone ( )
36
+ }
37
+
38
+ #[ cfg( all( test, target_os = "macos" ) ) ]
39
+ fn replace_test_managed_preferences_override ( value : Option < String > ) -> Option < String > {
40
+ let mut guard = lock_test_managed_preferences_override_storage ( ) ;
41
+ std:: mem:: replace ( & mut * guard, value)
42
+ }
43
+
44
+ #[ cfg( all( test, target_os = "macos" ) ) ]
45
+ fn lock_test_managed_preferences_override_storage ( ) -> std:: sync:: MutexGuard < ' static , Option < String > >
46
+ {
47
+ match test_managed_preferences_override_storage ( ) . lock ( ) {
48
+ Ok ( guard) => guard,
49
+ Err ( poisoned) => poisoned. into_inner ( ) ,
50
+ }
51
+ }
52
+
13
53
// Configuration layering pipeline (top overrides bottom):
14
54
//
15
55
// +-------------------------+
@@ -29,6 +69,20 @@ const CONFIG_OVERRIDE_TOML_FILE: &str = "config_override.toml";
29
69
// (*) Only available on macOS via managed device profiles.
30
70
31
71
pub fn load_config_as_toml ( codex_home : & Path ) -> io:: Result < TomlValue > {
72
+ let LoadedConfigLayers {
73
+ mut base,
74
+ override_layer,
75
+ managed_layer,
76
+ } = load_config_layers ( codex_home) ?;
77
+
78
+ for overlay in [ override_layer, managed_layer] . into_iter ( ) . flatten ( ) {
79
+ merge_toml_values ( & mut base, & overlay) ;
80
+ }
81
+
82
+ Ok ( base)
83
+ }
84
+
85
+ pub ( crate ) fn load_config_layers ( codex_home : & Path ) -> io:: Result < LoadedConfigLayers > {
32
86
let user_config_path = codex_home. join ( CONFIG_TOML_FILE ) ;
33
87
let override_config_path = codex_home. join ( CONFIG_OVERRIDE_TOML_FILE ) ;
34
88
@@ -42,13 +96,11 @@ pub fn load_config_as_toml(codex_home: &Path) -> io::Result<TomlValue> {
42
96
let override_config = join_config_result ( override_handle, "config_override.toml" ) ?;
43
97
let managed_config = join_config_result ( managed_handle, "managed preferences" ) ?;
44
98
45
- let mut merged = user_config. unwrap_or_else ( default_empty_table) ;
46
-
47
- for overlay in [ override_config, managed_config] . into_iter ( ) . flatten ( ) {
48
- merge_toml_values ( & mut merged, & overlay) ;
49
- }
50
-
51
- Ok ( merged)
99
+ Ok ( LoadedConfigLayers {
100
+ base : user_config. unwrap_or_else ( default_empty_table) ,
101
+ override_layer : override_config,
102
+ managed_layer : managed_config,
103
+ } )
52
104
} )
53
105
}
54
106
@@ -101,7 +153,7 @@ fn read_config_from_path(path: &Path, log_missing_as_info: bool) -> io::Result<O
101
153
}
102
154
}
103
155
104
- fn merge_toml_values ( base : & mut TomlValue , overlay : & TomlValue ) {
156
+ pub ( crate ) fn merge_toml_values ( base : & mut TomlValue , overlay : & TomlValue ) {
105
157
if let TomlValue :: Table ( overlay_table) = overlay
106
158
&& let TomlValue :: Table ( base_table) = base
107
159
{
@@ -131,7 +183,7 @@ fn load_managed_admin_config_impl() -> io::Result<Option<TomlValue>> {
131
183
132
184
#[ cfg( test) ]
133
185
{
134
- if let Ok ( encoded) = std :: env :: var ( "CODEX_TEST_MANAGED_PREFERENCES_BASE64" ) {
186
+ if let Some ( encoded) = test_managed_preferences_override ( ) {
135
187
let trimmed = encoded. trim ( ) ;
136
188
if trimmed. is_empty ( ) {
137
189
return Ok ( None ) ;
@@ -206,39 +258,34 @@ mod tests {
206
258
use tempfile:: tempdir;
207
259
208
260
#[ cfg( target_os = "macos" ) ]
209
- const MANAGED_ENV : & str = "CODEX_TEST_MANAGED_PREFERENCES_BASE64" ;
210
-
211
- #[ cfg( target_os = "macos" ) ]
212
- use base64:: Engine as _;
213
-
214
- #[ cfg( target_os = "macos" ) ]
215
- struct ManagedEnvGuard {
261
+ struct ManagedPreferencesOverrideGuard {
216
262
previous : Option < String > ,
217
263
}
218
264
219
265
#[ cfg( target_os = "macos" ) ]
220
- impl ManagedEnvGuard {
266
+ impl ManagedPreferencesOverrideGuard {
267
+ fn clear ( ) -> Self {
268
+ Self :: set ( "" )
269
+ }
270
+
221
271
fn set ( value : & str ) -> Self {
222
- let previous = std :: env :: var ( MANAGED_ENV ) . ok ( ) ;
223
- std :: env :: set_var ( MANAGED_ENV , value) ;
272
+ let previous =
273
+ super :: replace_test_managed_preferences_override ( Some ( value. to_string ( ) ) ) ;
224
274
Self { previous }
225
275
}
226
276
}
227
277
228
278
#[ cfg( target_os = "macos" ) ]
229
- impl Drop for ManagedEnvGuard {
279
+ impl Drop for ManagedPreferencesOverrideGuard {
230
280
fn drop ( & mut self ) {
231
- match & self . previous {
232
- Some ( prev) => std:: env:: set_var ( MANAGED_ENV , prev) ,
233
- None => std:: env:: remove_var ( MANAGED_ENV ) ,
234
- }
281
+ super :: replace_test_managed_preferences_override ( self . previous . clone ( ) ) ;
235
282
}
236
283
}
237
284
238
285
#[ test]
239
286
fn merges_override_layer_on_top ( ) {
240
287
#[ cfg( target_os = "macos" ) ]
241
- let _guard = ManagedEnvGuard :: set ( "" ) ;
288
+ let _guard = ManagedPreferencesOverrideGuard :: clear ( ) ;
242
289
243
290
let tmp = tempdir ( ) . expect ( "tempdir" ) ;
244
291
std:: fs:: write (
@@ -279,7 +326,7 @@ extra = true
279
326
#[ test]
280
327
fn returns_empty_when_all_layers_missing ( ) {
281
328
#[ cfg( target_os = "macos" ) ]
282
- let _guard = ManagedEnvGuard :: set ( "" ) ;
329
+ let _guard = ManagedPreferencesOverrideGuard :: clear ( ) ;
283
330
284
331
let tmp = tempdir ( ) . expect ( "tempdir" ) ;
285
332
let loaded = load_config_as_toml ( tmp. path ( ) ) . expect ( "load config" ) ;
@@ -293,14 +340,13 @@ extra = true
293
340
#[ cfg( target_os = "macos" ) ]
294
341
#[ test]
295
342
fn managed_preferences_take_highest_precedence ( ) {
296
- let _guard = ManagedEnvGuard :: set ( "" ) ;
297
343
let managed_payload = r#"
298
344
[nested]
299
345
value = "managed"
300
346
flag = false
301
347
"# ;
302
348
let encoded = super :: BASE64_STANDARD . encode ( managed_payload. as_bytes ( ) ) ;
303
- std :: env :: set_var ( MANAGED_ENV , encoded) ;
349
+ let _guard = ManagedPreferencesOverrideGuard :: set ( & encoded) ;
304
350
305
351
let tmp = tempdir ( ) . expect ( "tempdir" ) ;
306
352
std:: fs:: write (
0 commit comments