@@ -29,32 +29,44 @@ impl Metadata {
2929 pub fn workspace_root ( & self ) -> PathBuf {
3030 let mut root = self . workspace_root . lock ( ) . unwrap ( ) ;
3131 if root. is_none ( ) {
32- use serde:: Deserialize ;
33- use std:: process:: Command ;
34-
35- let cargo = crate :: env ( "CARGO" ) . unwrap ( ) ;
36-
37- let output = Command :: new ( cargo)
38- . args ( [ "metadata" , "--format-version=1" , "--no-deps" ] )
39- . current_dir ( & self . manifest_dir )
40- . env_remove ( "__CARGO_FIX_PLZ" )
41- . output ( )
42- . expect ( "Could not fetch metadata" ) ;
43-
44- #[ derive( Deserialize ) ]
45- struct CargoMetadata {
46- workspace_root : PathBuf ,
47- }
48-
49- let metadata: CargoMetadata =
50- serde_json:: from_slice ( & output. stdout ) . expect ( "Invalid `cargo metadata` output" ) ;
51-
52- * root = Some ( metadata. workspace_root ) ;
32+ * root = Some ( resolve_workspace_root (
33+ crate :: env ( "SQLX_WORKSPACE_DIR" ) . ok ( ) . map ( PathBuf :: from) ,
34+ || {
35+ use serde:: Deserialize ;
36+ use std:: process:: Command ;
37+
38+ let cargo = crate :: env ( "CARGO" ) . unwrap ( ) ;
39+
40+ let output = Command :: new ( cargo)
41+ . args ( [ "metadata" , "--format-version=1" , "--no-deps" ] )
42+ . current_dir ( & self . manifest_dir )
43+ . env_remove ( "__CARGO_FIX_PLZ" )
44+ . output ( )
45+ . expect ( "Could not fetch metadata" ) ;
46+
47+ #[ derive( Deserialize ) ]
48+ struct CargoMetadata {
49+ workspace_root : PathBuf ,
50+ }
51+
52+ let metadata: CargoMetadata = serde_json:: from_slice ( & output. stdout )
53+ . expect ( "Invalid `cargo metadata` output" ) ;
54+
55+ metadata. workspace_root
56+ } ,
57+ ) ) ;
5358 }
5459 root. clone ( ) . unwrap ( )
5560 }
5661}
5762
63+ fn resolve_workspace_root (
64+ override_dir : Option < PathBuf > ,
65+ cargo_fallback : impl FnOnce ( ) -> PathBuf ,
66+ ) -> PathBuf {
67+ override_dir. unwrap_or_else ( cargo_fallback)
68+ }
69+
5870pub fn try_for_crate ( ) -> crate :: Result < Arc < Metadata > > {
5971 /// The `MtimeCache` in this type covers the config itself,
6072 /// any changes to which will indirectly invalidate the loaded env vars as well.
@@ -89,7 +101,7 @@ pub fn try_for_crate() -> crate::Result<Arc<Metadata>> {
89101 } )
90102}
91103
92- fn load_env (
104+ fn load_from_dotenv (
93105 manifest_dir : & Path ,
94106 config : & Config ,
95107 builder : & mut MtimeCacheBuilder ,
@@ -143,20 +155,84 @@ fn load_env(
143155 }
144156 }
145157
158+ Ok ( Arc :: new ( from_dotenv) )
159+ }
160+
161+ fn load_env (
162+ manifest_dir : & Path ,
163+ config : & Config ,
164+ builder : & mut MtimeCacheBuilder ,
165+ ) -> crate :: Result < Arc < MacrosEnv > > {
166+ let database_url_env = crate :: env_opt ( config. common . database_url_var ( ) ) ?;
167+ let offline_dir_env = crate :: env_opt ( "SQLX_OFFLINE_DIR" ) ?. map ( PathBuf :: from) ;
168+ let offline_env = crate :: env_opt ( "SQLX_OFFLINE" ) ?. map ( |val| is_truthy_bool ( & val) ) ;
169+
170+ // Don't load .env files if all environment variables are set: we may be in
171+ // a non-Cargo build system like buck2.
172+ let dotenv = if database_url_env. is_none ( ) || offline_dir_env. is_none ( ) || offline_env. is_none ( )
173+ {
174+ Some ( load_from_dotenv ( manifest_dir, config, builder) ?)
175+ } else {
176+ None
177+ } ;
178+
146179 Ok ( Arc :: new ( MacrosEnv {
147- // Make set variables take precedent
148- database_url : crate :: env_opt ( config. common . database_url_var ( ) ) ?
149- . or ( from_dotenv. database_url ) ,
150- offline_dir : crate :: env_opt ( "SQLX_OFFLINE_DIR" ) ?
151- . map ( PathBuf :: from)
152- . or ( from_dotenv. offline_dir ) ,
153- offline : crate :: env_opt ( "SQLX_OFFLINE" ) ?
154- . map ( |val| is_truthy_bool ( & val) )
155- . or ( from_dotenv. offline ) ,
180+ // Make set variables take precedence
181+ database_url : database_url_env. or_else ( || dotenv. as_ref ( ) ?. database_url . clone ( ) ) ,
182+ offline_dir : offline_dir_env. or_else ( || dotenv. as_ref ( ) ?. offline_dir . clone ( ) ) ,
183+ offline : offline_env. or_else ( || dotenv. as_ref ( ) ?. offline ) ,
156184 } ) )
157185}
158186
159187/// Returns `true` if `val` is `"true"`,
160188fn is_truthy_bool ( val : & str ) -> bool {
161189 val. eq_ignore_ascii_case ( "true" ) || val == "1"
162190}
191+
192+ #[ cfg( test) ]
193+ mod tests {
194+ use super :: * ;
195+
196+ #[ test]
197+ fn load_from_dotenv_reads_env_file ( ) {
198+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
199+ std:: fs:: write (
200+ dir. path ( ) . join ( ".env" ) ,
201+ "DATABASE_URL=postgres://test\n SQLX_OFFLINE_DIR=/some/dir\n SQLX_OFFLINE=true\n " ,
202+ )
203+ . unwrap ( ) ;
204+
205+ let cache: MtimeCache < Arc < MacrosEnv > > = MtimeCache :: new ( ) ;
206+ let env = cache
207+ . get_or_try_init ( |builder| load_from_dotenv ( dir. path ( ) , & Config :: default ( ) , builder) )
208+ . unwrap ( ) ;
209+
210+ assert_eq ! ( env. database_url. as_deref( ) , Some ( "postgres://test" ) ) ;
211+ assert_eq ! ( env. offline_dir, Some ( PathBuf :: from( "/some/dir" ) ) ) ;
212+ assert_eq ! ( env. offline, Some ( true ) ) ;
213+ }
214+
215+ #[ test]
216+ fn load_from_dotenv_empty_when_no_env_file ( ) {
217+ // The ancestor walk finds nothing as long as no ancestor of the OS temp dir has a .env.
218+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
219+
220+ let cache: MtimeCache < Arc < MacrosEnv > > = MtimeCache :: new ( ) ;
221+ let env = cache
222+ . get_or_try_init ( |builder| load_from_dotenv ( dir. path ( ) , & Config :: default ( ) , builder) )
223+ . unwrap ( ) ;
224+
225+ assert ! ( env. database_url. is_none( ) ) ;
226+ assert ! ( env. offline_dir. is_none( ) ) ;
227+ assert ! ( env. offline. is_none( ) ) ;
228+ }
229+
230+ #[ test]
231+ fn resolve_workspace_root_prefers_override ( ) {
232+ let dir = PathBuf :: from ( "/fake/workspace" ) ;
233+ let result = resolve_workspace_root ( Some ( dir. clone ( ) ) , || {
234+ panic ! ( "cargo fallback must not be called when override is set" )
235+ } ) ;
236+ assert_eq ! ( result, dir) ;
237+ }
238+ }
0 commit comments