@@ -27,6 +27,9 @@ pub struct ProjectMetadata {
2727 /// The raw options
2828 pub ( super ) options : Options ,
2929
30+ /// Config file to override any discovered configuration
31+ pub ( super ) config_file_override : Option < SystemPathBuf > ,
32+
3033 /// Paths of configurations other than the project's configuration that were combined into [`Self::options`].
3134 ///
3235 /// This field stores the paths of the configuration files, mainly for
@@ -45,9 +48,35 @@ impl ProjectMetadata {
4548 root,
4649 extra_configuration_paths : Vec :: default ( ) ,
4750 options : Options :: default ( ) ,
51+ config_file_override : None ,
4852 }
4953 }
5054
55+ pub fn from_config_file (
56+ path : SystemPathBuf ,
57+ root : SystemPathBuf ,
58+ system : & dyn System ,
59+ ) -> Result < Self , ProjectMetadataError > {
60+ tracing:: debug!( "Using overridden configuration file at '{path}'" ) ;
61+
62+ let config_file = ConfigurationFile :: from_path ( path. clone ( ) , system) . map_err ( |error| {
63+ ProjectMetadataError :: ConfigurationFileError {
64+ source : Box :: new ( error) ,
65+ path : path. clone ( ) ,
66+ }
67+ } ) ?;
68+
69+ let options = config_file. into_options ( ) ;
70+
71+ Ok ( Self {
72+ name : Name :: new ( root. file_name ( ) . unwrap_or ( "root" ) ) ,
73+ root,
74+ options,
75+ extra_configuration_paths : Vec :: new ( ) ,
76+ config_file_override : Some ( path) ,
77+ } )
78+ }
79+
5180 /// Loads a project from a `pyproject.toml` file.
5281 pub ( crate ) fn from_pyproject (
5382 pyproject : PyProject ,
@@ -92,6 +121,7 @@ impl ProjectMetadata {
92121 root,
93122 options,
94123 extra_configuration_paths : Vec :: new ( ) ,
124+ config_file_override : None ,
95125 } )
96126 }
97127
@@ -106,11 +136,11 @@ impl ProjectMetadata {
106136 pub fn discover (
107137 path : & SystemPath ,
108138 system : & dyn System ,
109- ) -> Result < ProjectMetadata , ProjectDiscoveryError > {
139+ ) -> Result < ProjectMetadata , ProjectMetadataError > {
110140 tracing:: debug!( "Searching for a project in '{path}'" ) ;
111141
112142 if !system. is_directory ( path) {
113- return Err ( ProjectDiscoveryError :: NotADirectory ( path. to_path_buf ( ) ) ) ;
143+ return Err ( ProjectMetadataError :: NotADirectory ( path. to_path_buf ( ) ) ) ;
114144 }
115145
116146 let mut closest_project: Option < ProjectMetadata > = None ;
@@ -125,7 +155,7 @@ impl ProjectMetadata {
125155 ) {
126156 Ok ( pyproject) => Some ( pyproject) ,
127157 Err ( error) => {
128- return Err ( ProjectDiscoveryError :: InvalidPyProject {
158+ return Err ( ProjectMetadataError :: InvalidPyProject {
129159 path : pyproject_path,
130160 source : Box :: new ( error) ,
131161 } ) ;
@@ -144,7 +174,7 @@ impl ProjectMetadata {
144174 ) {
145175 Ok ( options) => options,
146176 Err ( error) => {
147- return Err ( ProjectDiscoveryError :: InvalidTyToml {
177+ return Err ( ProjectMetadataError :: InvalidTyToml {
148178 path : ty_toml_path,
149179 source : Box :: new ( error) ,
150180 } ) ;
@@ -171,7 +201,7 @@ impl ProjectMetadata {
171201 . and_then ( |pyproject| pyproject. project . as_ref ( ) ) ,
172202 )
173203 . map_err ( |err| {
174- ProjectDiscoveryError :: InvalidRequiresPythonConstraint {
204+ ProjectMetadataError :: InvalidRequiresPythonConstraint {
175205 source : err,
176206 path : pyproject_path,
177207 }
@@ -185,7 +215,7 @@ impl ProjectMetadata {
185215 let metadata =
186216 ProjectMetadata :: from_pyproject ( pyproject, project_root. to_path_buf ( ) )
187217 . map_err (
188- |err| ProjectDiscoveryError :: InvalidRequiresPythonConstraint {
218+ |err| ProjectMetadataError :: InvalidRequiresPythonConstraint {
189219 source : err,
190220 path : pyproject_path,
191221 } ,
@@ -281,7 +311,7 @@ impl ProjectMetadata {
281311}
282312
283313#[ derive( Debug , Error ) ]
284- pub enum ProjectDiscoveryError {
314+ pub enum ProjectMetadataError {
285315 #[ error( "project path '{0}' is not a directory" ) ]
286316 NotADirectory ( SystemPathBuf ) ,
287317
@@ -302,6 +332,12 @@ pub enum ProjectDiscoveryError {
302332 source : ResolveRequiresPythonError ,
303333 path : SystemPathBuf ,
304334 } ,
335+
336+ #[ error( "Error loading configuration file at {path}: {source}" ) ]
337+ ConfigurationFileError {
338+ source : Box < ConfigurationFileError > ,
339+ path : SystemPathBuf ,
340+ } ,
305341}
306342
307343#[ cfg( test) ]
@@ -313,7 +349,7 @@ mod tests {
313349 use ruff_db:: system:: { SystemPathBuf , TestSystem } ;
314350 use ruff_python_ast:: PythonVersion ;
315351
316- use crate :: { ProjectDiscoveryError , ProjectMetadata } ;
352+ use crate :: { ProjectMetadata , ProjectMetadataError } ;
317353
318354 #[ test]
319355 fn project_without_pyproject ( ) -> anyhow:: Result < ( ) > {
@@ -336,6 +372,7 @@ mod tests {
336372 name: Name("app"),
337373 root: "/app",
338374 options: Options(),
375+ config_file_override: None,
339376 )
340377 "# ) ;
341378 } ) ;
@@ -374,6 +411,7 @@ mod tests {
374411 name: Name("backend"),
375412 root: "/app",
376413 options: Options(),
414+ config_file_override: None,
377415 )
378416 "# ) ;
379417 } ) ;
@@ -471,6 +509,7 @@ expected `.`, `]`
471509 root: Some("src"),
472510 )),
473511 ),
512+ config_file_override: None,
474513 )
475514 "# ) ;
476515 } ) ;
@@ -521,6 +560,7 @@ expected `.`, `]`
521560 root: Some("src"),
522561 )),
523562 ),
563+ config_file_override: None,
524564 )
525565 "# ) ;
526566 } ) ;
@@ -561,6 +601,7 @@ expected `.`, `]`
561601 name: Name("nested-project"),
562602 root: "/app/packages/a",
563603 options: Options(),
604+ config_file_override: None,
564605 )
565606 "# ) ;
566607 } ) ;
@@ -608,6 +649,7 @@ expected `.`, `]`
608649 r#python-version: Some("3.10"),
609650 )),
610651 ),
652+ config_file_override: None,
611653 )
612654 "# ) ;
613655 } ) ;
@@ -663,6 +705,7 @@ expected `.`, `]`
663705 root: Some("src"),
664706 )),
665707 ),
708+ config_file_override: None,
666709 )
667710 "# ) ;
668711 } ) ;
@@ -948,7 +991,7 @@ expected `.`, `]`
948991 }
949992
950993 #[ track_caller]
951- fn assert_error_eq ( error : & ProjectDiscoveryError , message : & str ) {
994+ fn assert_error_eq ( error : & ProjectMetadataError , message : & str ) {
952995 assert_eq ! ( error. to_string( ) . replace( '\\' , "/" ) , message) ;
953996 }
954997
0 commit comments