@@ -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,33 @@ 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+ let options = config_file. into_options ( ) ;
69+ Ok ( Self {
70+ name : Name :: new ( root. file_name ( ) . unwrap_or ( "root" ) ) ,
71+ root,
72+ options,
73+ extra_configuration_paths : Vec :: new ( ) ,
74+ config_file_override : Some ( path) ,
75+ } )
76+ }
77+
5178 /// Loads a project from a `pyproject.toml` file.
5279 pub ( crate ) fn from_pyproject (
5380 pyproject : PyProject ,
@@ -92,6 +119,7 @@ impl ProjectMetadata {
92119 root,
93120 options,
94121 extra_configuration_paths : Vec :: new ( ) ,
122+ config_file_override : None ,
95123 } )
96124 }
97125
@@ -106,11 +134,11 @@ impl ProjectMetadata {
106134 pub fn discover (
107135 path : & SystemPath ,
108136 system : & dyn System ,
109- ) -> Result < ProjectMetadata , ProjectDiscoveryError > {
137+ ) -> Result < ProjectMetadata , ProjectMetadataError > {
110138 tracing:: debug!( "Searching for a project in '{path}'" ) ;
111139
112140 if !system. is_directory ( path) {
113- return Err ( ProjectDiscoveryError :: NotADirectory ( path. to_path_buf ( ) ) ) ;
141+ return Err ( ProjectMetadataError :: NotADirectory ( path. to_path_buf ( ) ) ) ;
114142 }
115143
116144 let mut closest_project: Option < ProjectMetadata > = None ;
@@ -125,7 +153,7 @@ impl ProjectMetadata {
125153 ) {
126154 Ok ( pyproject) => Some ( pyproject) ,
127155 Err ( error) => {
128- return Err ( ProjectDiscoveryError :: InvalidPyProject {
156+ return Err ( ProjectMetadataError :: InvalidPyProject {
129157 path : pyproject_path,
130158 source : Box :: new ( error) ,
131159 } ) ;
@@ -144,7 +172,7 @@ impl ProjectMetadata {
144172 ) {
145173 Ok ( options) => options,
146174 Err ( error) => {
147- return Err ( ProjectDiscoveryError :: InvalidTyToml {
175+ return Err ( ProjectMetadataError :: InvalidTyToml {
148176 path : ty_toml_path,
149177 source : Box :: new ( error) ,
150178 } ) ;
@@ -171,7 +199,7 @@ impl ProjectMetadata {
171199 . and_then ( |pyproject| pyproject. project . as_ref ( ) ) ,
172200 )
173201 . map_err ( |err| {
174- ProjectDiscoveryError :: InvalidRequiresPythonConstraint {
202+ ProjectMetadataError :: InvalidRequiresPythonConstraint {
175203 source : err,
176204 path : pyproject_path,
177205 }
@@ -185,7 +213,7 @@ impl ProjectMetadata {
185213 let metadata =
186214 ProjectMetadata :: from_pyproject ( pyproject, project_root. to_path_buf ( ) )
187215 . map_err (
188- |err| ProjectDiscoveryError :: InvalidRequiresPythonConstraint {
216+ |err| ProjectMetadataError :: InvalidRequiresPythonConstraint {
189217 source : err,
190218 path : pyproject_path,
191219 } ,
@@ -281,7 +309,7 @@ impl ProjectMetadata {
281309}
282310
283311#[ derive( Debug , Error ) ]
284- pub enum ProjectDiscoveryError {
312+ pub enum ProjectMetadataError {
285313 #[ error( "project path '{0}' is not a directory" ) ]
286314 NotADirectory ( SystemPathBuf ) ,
287315
@@ -302,6 +330,12 @@ pub enum ProjectDiscoveryError {
302330 source : ResolveRequiresPythonError ,
303331 path : SystemPathBuf ,
304332 } ,
333+
334+ #[ error( "Error loading configuration file at {path}: {source}" ) ]
335+ ConfigurationFileError {
336+ source : Box < ConfigurationFileError > ,
337+ path : SystemPathBuf ,
338+ } ,
305339}
306340
307341#[ cfg( test) ]
@@ -313,7 +347,7 @@ mod tests {
313347 use ruff_db:: system:: { SystemPathBuf , TestSystem } ;
314348 use ruff_python_ast:: PythonVersion ;
315349
316- use crate :: { ProjectDiscoveryError , ProjectMetadata } ;
350+ use crate :: { ProjectMetadata , ProjectMetadataError } ;
317351
318352 #[ test]
319353 fn project_without_pyproject ( ) -> anyhow:: Result < ( ) > {
@@ -948,7 +982,7 @@ expected `.`, `]`
948982 }
949983
950984 #[ track_caller]
951- fn assert_error_eq ( error : & ProjectDiscoveryError , message : & str ) {
985+ fn assert_error_eq ( error : & ProjectMetadataError , message : & str ) {
952986 assert_eq ! ( error. to_string( ) . replace( '\\' , "/" ) , message) ;
953987 }
954988
0 commit comments