aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_project_model')
-rw-r--r--crates/ra_project_model/src/lib.rs221
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
80impl ProjectWorkspace { 80#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81 pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { 81pub enum ProjectRoot {
82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) 82 ProjectJson(PathBuf),
83 CargoToml(PathBuf),
84}
85
86impl 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
186impl 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
472fn 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
489fn 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
502fn 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
514fn 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
553pub fn get_rustc_cfg_options() -> CfgOptions { 574pub fn get_rustc_cfg_options() -> CfgOptions {
554 let mut cfg_options = CfgOptions::default(); 575 let mut cfg_options = CfgOptions::default();
555 576