@@ -5,9 +5,8 @@ mod json_project;
55mod sysroot;
66
77use std:: {
8- error:: Error ,
98 fs:: { read_dir, File , ReadDir } ,
10- io:: BufReader ,
9+ io:: { self , BufReader } ,
1110 path:: { Path , PathBuf } ,
1211 process:: Command ,
1312} ;
@@ -25,25 +24,6 @@ pub use crate::{
2524} ;
2625pub use ra_proc_macro:: ProcMacroClient ;
2726
28- #[ derive( Clone , PartialEq , Eq , Hash , Debug ) ]
29- pub struct CargoTomlNotFoundError {
30- pub searched_at : PathBuf ,
31- pub reason : String ,
32- }
33-
34- impl std:: fmt:: Display for CargoTomlNotFoundError {
35- fn fmt ( & self , fmt : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
36- write ! (
37- fmt,
38- "can't find Cargo.toml at {}, due to {}" ,
39- self . searched_at. display( ) ,
40- self . reason
41- )
42- }
43- }
44-
45- impl Error for CargoTomlNotFoundError { }
46-
4727#[ derive( Debug , Clone ) ]
4828pub enum ProjectWorkspace {
4929 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
@@ -77,31 +57,119 @@ impl PackageRoot {
7757 }
7858}
7959
80- impl ProjectWorkspace {
81- pub fn discover ( path : & Path , cargo_features : & CargoConfig ) -> Result < ProjectWorkspace > {
82- ProjectWorkspace :: discover_with_sysroot ( path, true , cargo_features)
60+ #[ derive( Debug , Clone , PartialEq , Eq , Hash ) ]
61+ pub enum ProjectRoot {
62+ ProjectJson ( PathBuf ) ,
63+ CargoToml ( PathBuf ) ,
64+ }
65+
66+ impl ProjectRoot {
67+ pub fn from_manifest_file ( path : PathBuf ) -> Result < ProjectRoot > {
68+ if path. ends_with ( "rust-project.json" ) {
69+ return Ok ( ProjectRoot :: ProjectJson ( path) ) ;
70+ }
71+ if path. ends_with ( "Cargo.toml" ) {
72+ return Ok ( ProjectRoot :: CargoToml ( path) ) ;
73+ }
74+ bail ! ( "project root must point to Cargo.toml or rust-project.json: {}" , path. display( ) )
8375 }
8476
85- pub fn discover_with_sysroot (
86- path : & Path ,
87- with_sysroot : bool ,
77+ pub fn discover_single ( path : & Path ) -> Result < ProjectRoot > {
78+ let mut candidates = ProjectRoot :: discover ( path) ?;
79+ let res = match candidates. pop ( ) {
80+ None => bail ! ( "no projects" ) ,
81+ Some ( it) => it,
82+ } ;
83+
84+ if !candidates. is_empty ( ) {
85+ bail ! ( "more than one project" )
86+ }
87+ Ok ( res)
88+ }
89+
90+ pub fn discover ( path : & Path ) -> io:: Result < Vec < ProjectRoot > > {
91+ if let Some ( project_json) = find_rust_project_json ( path) {
92+ return Ok ( vec ! [ ProjectRoot :: ProjectJson ( project_json) ] ) ;
93+ }
94+ return find_cargo_toml ( path)
95+ . map ( |paths| paths. into_iter ( ) . map ( ProjectRoot :: CargoToml ) . collect ( ) ) ;
96+
97+ fn find_rust_project_json ( path : & Path ) -> Option < PathBuf > {
98+ if path. ends_with ( "rust-project.json" ) {
99+ return Some ( path. to_path_buf ( ) ) ;
100+ }
101+
102+ let mut curr = Some ( path) ;
103+ while let Some ( path) = curr {
104+ let candidate = path. join ( "rust-project.json" ) ;
105+ if candidate. exists ( ) {
106+ return Some ( candidate) ;
107+ }
108+ curr = path. parent ( ) ;
109+ }
110+
111+ None
112+ }
113+
114+ fn find_cargo_toml ( path : & Path ) -> io:: Result < Vec < PathBuf > > {
115+ if path. ends_with ( "Cargo.toml" ) {
116+ return Ok ( vec ! [ path. to_path_buf( ) ] ) ;
117+ }
118+
119+ if let Some ( p) = find_cargo_toml_in_parent_dir ( path) {
120+ return Ok ( vec ! [ p] ) ;
121+ }
122+
123+ let entities = read_dir ( path) ?;
124+ Ok ( find_cargo_toml_in_child_dir ( entities) )
125+ }
126+
127+ fn find_cargo_toml_in_parent_dir ( path : & Path ) -> Option < PathBuf > {
128+ let mut curr = Some ( path) ;
129+ while let Some ( path) = curr {
130+ let candidate = path. join ( "Cargo.toml" ) ;
131+ if candidate. exists ( ) {
132+ return Some ( candidate) ;
133+ }
134+ curr = path. parent ( ) ;
135+ }
136+
137+ None
138+ }
139+
140+ fn find_cargo_toml_in_child_dir ( entities : ReadDir ) -> Vec < PathBuf > {
141+ // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
142+ let mut valid_canditates = vec ! [ ] ;
143+ for entity in entities. filter_map ( Result :: ok) {
144+ let candidate = entity. path ( ) . join ( "Cargo.toml" ) ;
145+ if candidate. exists ( ) {
146+ valid_canditates. push ( candidate)
147+ }
148+ }
149+ valid_canditates
150+ }
151+ }
152+ }
153+
154+ impl ProjectWorkspace {
155+ pub fn load (
156+ root : ProjectRoot ,
88157 cargo_features : & CargoConfig ,
158+ with_sysroot : bool ,
89159 ) -> Result < ProjectWorkspace > {
90- match find_rust_project_json ( path) {
91- Some ( json_path) => {
92- let file = File :: open ( & json_path)
93- . with_context ( || format ! ( "Failed to open json file {}" , json_path. display( ) ) ) ?;
160+ let res = match root {
161+ ProjectRoot :: ProjectJson ( project_json) => {
162+ let file = File :: open ( & project_json) . with_context ( || {
163+ format ! ( "Failed to open json file {}" , project_json. display( ) )
164+ } ) ?;
94165 let reader = BufReader :: new ( file) ;
95- Ok ( ProjectWorkspace :: Json {
166+ ProjectWorkspace :: Json {
96167 project : from_reader ( reader) . with_context ( || {
97- format ! ( "Failed to deserialize json file {}" , json_path . display( ) )
168+ format ! ( "Failed to deserialize json file {}" , project_json . display( ) )
98169 } ) ?,
99- } )
170+ }
100171 }
101- None => {
102- let cargo_toml = find_cargo_toml ( path) . with_context ( || {
103- format ! ( "Failed to find Cargo.toml for path {}" , path. display( ) )
104- } ) ?;
172+ ProjectRoot :: CargoToml ( cargo_toml) => {
105173 let cargo = CargoWorkspace :: from_cargo_metadata ( & cargo_toml, cargo_features)
106174 . with_context ( || {
107175 format ! (
@@ -119,9 +187,11 @@ impl ProjectWorkspace {
119187 } else {
120188 Sysroot :: default ( )
121189 } ;
122- Ok ( ProjectWorkspace :: Cargo { cargo, sysroot } )
190+ ProjectWorkspace :: Cargo { cargo, sysroot }
123191 }
124- }
192+ } ;
193+
194+ Ok ( res)
125195 }
126196
127197 /// Returns the roots for the current `ProjectWorkspace`
@@ -469,87 +539,6 @@ impl ProjectWorkspace {
469539 }
470540}
471541
472- fn find_rust_project_json ( path : & Path ) -> Option < PathBuf > {
473- if path. ends_with ( "rust-project.json" ) {
474- return Some ( path. to_path_buf ( ) ) ;
475- }
476-
477- let mut curr = Some ( path) ;
478- while let Some ( path) = curr {
479- let candidate = path. join ( "rust-project.json" ) ;
480- if candidate. exists ( ) {
481- return Some ( candidate) ;
482- }
483- curr = path. parent ( ) ;
484- }
485-
486- None
487- }
488-
489- fn find_cargo_toml_in_parent_dir ( path : & Path ) -> Option < PathBuf > {
490- let mut curr = Some ( path) ;
491- while let Some ( path) = curr {
492- let candidate = path. join ( "Cargo.toml" ) ;
493- if candidate. exists ( ) {
494- return Some ( candidate) ;
495- }
496- curr = path. parent ( ) ;
497- }
498-
499- None
500- }
501-
502- fn find_cargo_toml_in_child_dir ( entities : ReadDir ) -> Vec < PathBuf > {
503- // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
504- let mut valid_canditates = vec ! [ ] ;
505- for entity in entities. filter_map ( Result :: ok) {
506- let candidate = entity. path ( ) . join ( "Cargo.toml" ) ;
507- if candidate. exists ( ) {
508- valid_canditates. push ( candidate)
509- }
510- }
511- valid_canditates
512- }
513-
514- fn find_cargo_toml ( path : & Path ) -> Result < PathBuf > {
515- if path. ends_with ( "Cargo.toml" ) {
516- return Ok ( path. to_path_buf ( ) ) ;
517- }
518-
519- if let Some ( p) = find_cargo_toml_in_parent_dir ( path) {
520- return Ok ( p) ;
521- }
522-
523- let entities = match read_dir ( path) {
524- Ok ( entities) => entities,
525- Err ( e) => {
526- return Err ( CargoTomlNotFoundError {
527- searched_at : path. to_path_buf ( ) ,
528- reason : format ! ( "file system error: {}" , e) ,
529- }
530- . into ( ) ) ;
531- }
532- } ;
533-
534- let mut valid_canditates = find_cargo_toml_in_child_dir ( entities) ;
535- match valid_canditates. len ( ) {
536- 1 => Ok ( valid_canditates. remove ( 0 ) ) ,
537- 0 => Err ( CargoTomlNotFoundError {
538- searched_at : path. to_path_buf ( ) ,
539- reason : "no Cargo.toml file found" . to_string ( ) ,
540- }
541- . into ( ) ) ,
542- _ => Err ( CargoTomlNotFoundError {
543- searched_at : path. to_path_buf ( ) ,
544- reason : format ! (
545- "multiple equally valid Cargo.toml files found: {:?}" ,
546- valid_canditates
547- ) ,
548- }
549- . into ( ) ) ,
550- }
551- }
552-
553542pub fn get_rustc_cfg_options ( ) -> CfgOptions {
554543 let mut cfg_options = CfgOptions :: default ( ) ;
555544
0 commit comments