diff options
Diffstat (limited to 'crates/ra_project_model')
-rw-r--r-- | crates/ra_project_model/src/lib.rs | 221 |
1 files changed, 121 insertions, 100 deletions
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 0ab64a1e0..df4be94dc 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -77,31 +77,131 @@ impl PackageRoot { | |||
77 | } | 77 | } |
78 | } | 78 | } |
79 | 79 | ||
80 | impl ProjectWorkspace { | 80 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
81 | pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { | 81 | pub enum ProjectRoot { |
82 | ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) | 82 | ProjectJson(PathBuf), |
83 | CargoToml(PathBuf), | ||
84 | } | ||
85 | |||
86 | impl ProjectRoot { | ||
87 | pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> { | ||
88 | if path.ends_with("rust-project.json") { | ||
89 | return Ok(ProjectRoot::ProjectJson(path)); | ||
90 | } | ||
91 | if path.ends_with("Cargo.toml") { | ||
92 | return Ok(ProjectRoot::CargoToml(path)); | ||
93 | } | ||
94 | bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) | ||
83 | } | 95 | } |
84 | 96 | ||
85 | pub fn discover_with_sysroot( | 97 | pub fn discover(path: &Path) -> Result<ProjectRoot, CargoTomlNotFoundError> { |
86 | path: &Path, | 98 | if let Some(project_json) = find_rust_project_json(path) { |
87 | with_sysroot: bool, | 99 | return Ok(ProjectRoot::ProjectJson(project_json)); |
100 | } | ||
101 | return find_cargo_toml(path).map(ProjectRoot::CargoToml); | ||
102 | |||
103 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
104 | if path.ends_with("rust-project.json") { | ||
105 | return Some(path.to_path_buf()); | ||
106 | } | ||
107 | |||
108 | let mut curr = Some(path); | ||
109 | while let Some(path) = curr { | ||
110 | let candidate = path.join("rust-project.json"); | ||
111 | if candidate.exists() { | ||
112 | return Some(candidate); | ||
113 | } | ||
114 | curr = path.parent(); | ||
115 | } | ||
116 | |||
117 | None | ||
118 | } | ||
119 | |||
120 | fn find_cargo_toml(path: &Path) -> Result<PathBuf, CargoTomlNotFoundError> { | ||
121 | if path.ends_with("Cargo.toml") { | ||
122 | return Ok(path.to_path_buf()); | ||
123 | } | ||
124 | |||
125 | if let Some(p) = find_cargo_toml_in_parent_dir(path) { | ||
126 | return Ok(p); | ||
127 | } | ||
128 | |||
129 | let entities = match read_dir(path) { | ||
130 | Ok(entities) => entities, | ||
131 | Err(e) => { | ||
132 | return Err(CargoTomlNotFoundError { | ||
133 | searched_at: path.to_path_buf(), | ||
134 | reason: format!("file system error: {}", e), | ||
135 | } | ||
136 | .into()); | ||
137 | } | ||
138 | }; | ||
139 | |||
140 | let mut valid_canditates = find_cargo_toml_in_child_dir(entities); | ||
141 | return match valid_canditates.len() { | ||
142 | 1 => Ok(valid_canditates.remove(0)), | ||
143 | 0 => Err(CargoTomlNotFoundError { | ||
144 | searched_at: path.to_path_buf(), | ||
145 | reason: "no Cargo.toml file found".to_string(), | ||
146 | } | ||
147 | .into()), | ||
148 | _ => Err(CargoTomlNotFoundError { | ||
149 | searched_at: path.to_path_buf(), | ||
150 | reason: format!( | ||
151 | "multiple equally valid Cargo.toml files found: {:?}", | ||
152 | valid_canditates | ||
153 | ), | ||
154 | } | ||
155 | .into()), | ||
156 | }; | ||
157 | } | ||
158 | |||
159 | fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> { | ||
160 | let mut curr = Some(path); | ||
161 | while let Some(path) = curr { | ||
162 | let candidate = path.join("Cargo.toml"); | ||
163 | if candidate.exists() { | ||
164 | return Some(candidate); | ||
165 | } | ||
166 | curr = path.parent(); | ||
167 | } | ||
168 | |||
169 | None | ||
170 | } | ||
171 | |||
172 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { | ||
173 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects | ||
174 | let mut valid_canditates = vec![]; | ||
175 | for entity in entities.filter_map(Result::ok) { | ||
176 | let candidate = entity.path().join("Cargo.toml"); | ||
177 | if candidate.exists() { | ||
178 | valid_canditates.push(candidate) | ||
179 | } | ||
180 | } | ||
181 | valid_canditates | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | impl ProjectWorkspace { | ||
187 | pub fn load( | ||
188 | root: ProjectRoot, | ||
88 | cargo_features: &CargoConfig, | 189 | cargo_features: &CargoConfig, |
190 | with_sysroot: bool, | ||
89 | ) -> Result<ProjectWorkspace> { | 191 | ) -> Result<ProjectWorkspace> { |
90 | match find_rust_project_json(path) { | 192 | let res = match root { |
91 | Some(json_path) => { | 193 | ProjectRoot::ProjectJson(project_json) => { |
92 | let file = File::open(&json_path) | 194 | let file = File::open(&project_json).with_context(|| { |
93 | .with_context(|| format!("Failed to open json file {}", json_path.display()))?; | 195 | format!("Failed to open json file {}", project_json.display()) |
196 | })?; | ||
94 | let reader = BufReader::new(file); | 197 | let reader = BufReader::new(file); |
95 | Ok(ProjectWorkspace::Json { | 198 | ProjectWorkspace::Json { |
96 | project: from_reader(reader).with_context(|| { | 199 | project: from_reader(reader).with_context(|| { |
97 | format!("Failed to deserialize json file {}", json_path.display()) | 200 | format!("Failed to deserialize json file {}", project_json.display()) |
98 | })?, | 201 | })?, |
99 | }) | 202 | } |
100 | } | 203 | } |
101 | None => { | 204 | ProjectRoot::CargoToml(cargo_toml) => { |
102 | let cargo_toml = find_cargo_toml(path).with_context(|| { | ||
103 | format!("Failed to find Cargo.toml for path {}", path.display()) | ||
104 | })?; | ||
105 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) | 205 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) |
106 | .with_context(|| { | 206 | .with_context(|| { |
107 | format!( | 207 | format!( |
@@ -119,9 +219,11 @@ impl ProjectWorkspace { | |||
119 | } else { | 219 | } else { |
120 | Sysroot::default() | 220 | Sysroot::default() |
121 | }; | 221 | }; |
122 | Ok(ProjectWorkspace::Cargo { cargo, sysroot }) | 222 | ProjectWorkspace::Cargo { cargo, sysroot } |
123 | } | 223 | } |
124 | } | 224 | }; |
225 | |||
226 | Ok(res) | ||
125 | } | 227 | } |
126 | 228 | ||
127 | /// Returns the roots for the current `ProjectWorkspace` | 229 | /// Returns the roots for the current `ProjectWorkspace` |
@@ -469,87 +571,6 @@ impl ProjectWorkspace { | |||
469 | } | 571 | } |
470 | } | 572 | } |
471 | 573 | ||
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 | |||
553 | pub fn get_rustc_cfg_options() -> CfgOptions { | 574 | pub fn get_rustc_cfg_options() -> CfgOptions { |
554 | let mut cfg_options = CfgOptions::default(); | 575 | let mut cfg_options = CfgOptions::default(); |
555 | 576 | ||