1
- #[ cfg( procmacro2_semver_exempt) ]
2
- use std:: collections:: HashSet ;
1
+ use std:: cell:: RefCell ;
3
2
use std:: collections:: { hash_map, HashMap } ;
4
3
use std:: env:: VarError ;
5
- use std:: hash:: { BuildHasherDefault , DefaultHasher } ;
6
4
use std:: path:: { Path , PathBuf } ;
7
5
use std:: sync:: { Arc , LazyLock , Mutex } ;
8
6
use std:: { fs, io} ;
@@ -113,15 +111,20 @@ impl Metadata {
113
111
}
114
112
}
115
113
116
- static METADATA : LazyLock < Mutex < HashMap < String , Metadata > > > = LazyLock :: new ( Default :: default) ;
114
+ static METADATA : LazyLock < Mutex < HashMap < PathBuf , Arc < Metadata > > > > = LazyLock :: new ( Default :: default) ;
115
+ static CRATE_ENV_FILE_VARS : LazyLock < Mutex < HashMap < PathBuf , HashMap < String , String > > > > =
116
+ LazyLock :: new ( Default :: default) ;
117
+
118
+ thread_local ! {
119
+ static CURRENT_CRATE_MANIFEST_DIR : RefCell <PathBuf > = RefCell :: new( PathBuf :: new( ) ) ;
120
+ }
117
121
118
122
// If we are in a workspace, lookup `workspace_root` since `CARGO_MANIFEST_DIR` won't
119
123
// reflect the workspace dir: https://github.com/rust-lang/cargo/issues/3946
120
- fn init_metadata ( manifest_dir : & String ) -> crate :: Result < Metadata > {
121
- let manifest_dir: PathBuf = manifest_dir. into ( ) ;
124
+ fn init_metadata ( manifest_dir : & Path ) -> crate :: Result < Arc < Metadata > > {
122
125
let config = Config :: try_from_crate_or_default ( ) ?;
123
126
124
- load_env ( & manifest_dir, & config) ;
127
+ load_env ( manifest_dir, & config) ;
125
128
126
129
let offline = env ( "SQLX_OFFLINE" )
127
130
. map ( |s| s. eq_ignore_ascii_case ( "true" ) || s == "1" )
@@ -131,40 +134,41 @@ fn init_metadata(manifest_dir: &String) -> crate::Result<Metadata> {
131
134
132
135
let database_url = env ( config. common . database_url_var ( ) ) . ok ( ) ;
133
136
134
- Ok ( Metadata {
135
- manifest_dir,
137
+ Ok ( Arc :: new ( Metadata {
138
+ manifest_dir : manifest_dir . to_path_buf ( ) ,
136
139
offline,
137
140
database_url,
138
141
offline_dir,
139
142
config,
140
143
workspace_root : Arc :: new ( Mutex :: new ( None ) ) ,
141
- } )
144
+ } ) )
142
145
}
143
146
144
147
pub fn expand_input < ' a > (
145
148
input : QueryMacroInput ,
146
149
drivers : impl IntoIterator < Item = & ' a QueryDriver > ,
147
150
) -> crate :: Result < TokenStream > {
148
- let manifest_dir = env ( "CARGO_MANIFEST_DIR" ) . expect ( "`CARGO_MANIFEST_DIR` must be set" ) ;
151
+ // `CARGO_MANIFEST_DIR` can only be loaded from a real environment variable due to the filtering done
152
+ // by `load_env`, so the value of `CURRENT_CRATE_MANIFEST_DIR` does not matter here.
153
+ let manifest_dir =
154
+ PathBuf :: from ( env ( "CARGO_MANIFEST_DIR" ) . expect ( "`CARGO_MANIFEST_DIR` must be set" ) ) ;
155
+ CURRENT_CRATE_MANIFEST_DIR . set ( manifest_dir. clone ( ) ) ;
149
156
150
- let mut metadata_lock = METADATA
151
- . lock ( )
152
- // Just reset the metadata on error
153
- . unwrap_or_else ( |poison_err| {
154
- let mut guard = poison_err. into_inner ( ) ;
155
- * guard = Default :: default ( ) ;
156
- guard
157
- } ) ;
157
+ let mut metadata_lock = METADATA . lock ( ) . unwrap ( ) ;
158
158
159
159
let metadata = match metadata_lock. entry ( manifest_dir) {
160
- hash_map:: Entry :: Occupied ( occupied) => occupied. into_mut ( ) ,
160
+ hash_map:: Entry :: Occupied ( occupied) => Arc :: clone ( & occupied. get ( ) ) ,
161
161
hash_map:: Entry :: Vacant ( vacant) => {
162
162
let metadata = init_metadata ( vacant. key ( ) ) ?;
163
- vacant. insert ( metadata)
163
+ vacant. insert ( Arc :: clone ( & metadata) ) ;
164
+ metadata
164
165
}
165
166
} ;
166
167
167
- let data_source = match & metadata {
168
+ // Release the lock now so other expansions in other threads of this process can proceed concurrently.
169
+ drop ( metadata_lock) ;
170
+
171
+ let data_source = match & * metadata {
168
172
Metadata {
169
173
offline : false ,
170
174
database_url : Some ( db_url) ,
@@ -182,7 +186,7 @@ pub fn expand_input<'a>(
182
186
] ;
183
187
let Some ( data_file_path) = dirs
184
188
. iter ( )
185
- . filter_map ( |path| path ( metadata) )
189
+ . filter_map ( |path| path ( & metadata) )
186
190
. map ( |path| path. join ( & filename) )
187
191
. find ( |path| path. exists ( ) )
188
192
else {
@@ -416,10 +420,11 @@ where
416
420
Ok ( ret_tokens)
417
421
}
418
422
419
- static LOADED_ENV_VARS : Mutex < HashMap < String , String , BuildHasherDefault < DefaultHasher > > > =
420
- Mutex :: new ( HashMap :: with_hasher ( BuildHasherDefault :: new ( ) ) ) ;
421
-
422
- /// Get the value of an environment variable, telling the compiler about it if applicable.
423
+ /// Get the value of an environment variable for the current crate, telling the compiler about it if applicable.
424
+ ///
425
+ /// The current crate is determined by the `CURRENT_CRATE_MANIFEST_DIR` thread-local variable, which is assumed
426
+ /// to be set to match the crate whose macro is being expanded before this function is called. It is also assumed
427
+ /// that the expansion of this macro happens on a single thread.
423
428
fn env ( name : & str ) -> Result < String , std:: env:: VarError > {
424
429
#[ cfg( procmacro2_semver_exempt) ]
425
430
let tracked_value = Some ( proc_macro:: tracked_env:: var ( name) ) ;
@@ -428,26 +433,37 @@ fn env(name: &str) -> Result<String, std::env::VarError> {
428
433
429
434
match tracked_value. map_or_else ( || std:: env:: var ( name) , |var| var) {
430
435
Ok ( v) => Ok ( v) ,
431
- Err ( VarError :: NotPresent ) => LOADED_ENV_VARS
432
- . lock ( )
433
- . unwrap ( )
434
- . get ( name)
435
- . cloned ( )
436
+ Err ( VarError :: NotPresent ) => CURRENT_CRATE_MANIFEST_DIR
437
+ . with_borrow ( |manifest_dir| {
438
+ CRATE_ENV_FILE_VARS
439
+ . lock ( )
440
+ . unwrap ( )
441
+ . get ( manifest_dir)
442
+ . cloned ( )
443
+ } )
444
+ . and_then ( |env_file_vars| env_file_vars. get ( name) . cloned ( ) )
436
445
. ok_or ( VarError :: NotPresent ) ,
437
446
Err ( e) => Err ( e) ,
438
447
}
439
448
}
440
449
441
- /// Load configuration environment variables from a `.env` file, without overriding existing
442
- /// environment variables. If applicable, the compiler is informed about the loaded env vars
443
- /// and the `.env` files they may come from.
450
+ /// Load configuration environment variables from a `.env` file. If applicable, the compiler is
451
+ /// about the `.env` files they may come from.
444
452
fn load_env ( manifest_dir : & Path , config : & Config ) {
445
- let loadable_vars = [
446
- "DATABASE_URL" ,
447
- "SQLX_OFFLINE" ,
448
- "SQLX_OFFLINE_DIR" ,
449
- config. common . database_url_var ( ) ,
450
- ] ;
453
+ // A whitelist of environment variables to load from a `.env` file avoids
454
+ // such files overriding internal variables they should not (e.g., `CARGO`,
455
+ // `CARGO_MANIFEST_DIR`) when using the `env` function above.
456
+ let database_url_var = config. common . database_url_var ( ) ;
457
+ let loadable_vars = if database_url_var == "DATABASE_URL" {
458
+ & [ "DATABASE_URL" , "SQLX_OFFLINE" , "SQLX_OFFLINE_DIR" ] [ ..]
459
+ } else {
460
+ & [
461
+ "DATABASE_URL" ,
462
+ "SQLX_OFFLINE" ,
463
+ "SQLX_OFFLINE_DIR" ,
464
+ database_url_var,
465
+ ]
466
+ } ;
451
467
452
468
let ( found_dotenv, candidate_dotenv_paths) = find_dotenv ( manifest_dir) ;
453
469
@@ -461,26 +477,33 @@ fn load_env(manifest_dir: &Path, config: &Config) {
461
477
}
462
478
}
463
479
464
- if let Some ( dotenv_path ) = found_dotenv
480
+ let loaded_vars = found_dotenv
465
481
. then_some ( candidate_dotenv_paths)
466
482
. iter ( )
467
483
. flatten ( )
468
484
. last ( )
469
- {
470
- for dotenv_var_result in dotenvy:: from_path_iter ( dotenv_path)
471
- . ok ( )
472
- . into_iter ( )
473
- . flatten ( )
474
- {
475
- let Ok ( ( key, value) ) = dotenv_var_result else {
476
- continue ;
477
- } ;
485
+ . map ( |dotenv_path| {
486
+ dotenvy:: from_path_iter ( dotenv_path)
487
+ . ok ( )
488
+ . into_iter ( )
489
+ . flatten ( )
490
+ . filter_map ( |dotenv_var_result| match dotenv_var_result {
491
+ Ok ( ( key, value) )
492
+ if loadable_vars. contains ( & & * key) && std:: env:: var ( & key) . is_err ( ) =>
493
+ {
494
+ Some ( ( key, value) )
495
+ }
496
+ _ => None ,
497
+ } )
498
+ } )
499
+ . into_iter ( )
500
+ . flatten ( )
501
+ . collect ( ) ;
478
502
479
- if loadable_vars. contains ( & & * key) && std:: env:: var ( & key) . is_err ( ) {
480
- LOADED_ENV_VARS . lock ( ) . unwrap ( ) . insert ( key, value) ;
481
- }
482
- }
483
- }
503
+ CRATE_ENV_FILE_VARS
504
+ . lock ( )
505
+ . unwrap ( )
506
+ . insert ( manifest_dir. to_path_buf ( ) , loaded_vars) ;
484
507
}
485
508
486
509
fn find_dotenv ( mut dir : & Path ) -> ( bool , Vec < PathBuf > ) {
0 commit comments