aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_project_model/src/lib.rs')
-rw-r--r--crates/ra_project_model/src/lib.rs231
1 files changed, 110 insertions, 121 deletions
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 0ab64a1e0..03f2629da 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -5,9 +5,8 @@ mod json_project;
5mod sysroot; 5mod sysroot;
6 6
7use std::{ 7use std::{
8 error::Error,
9 fs::{read_dir, File, ReadDir}, 8 fs::{read_dir, File, ReadDir},
10 io::BufReader, 9 io::{self, BufReader},
11 path::{Path, PathBuf}, 10 path::{Path, PathBuf},
12 process::Command, 11 process::Command,
13}; 12};
@@ -25,25 +24,6 @@ pub use crate::{
25}; 24};
26pub use ra_proc_macro::ProcMacroClient; 25pub use ra_proc_macro::ProcMacroClient;
27 26
28#[derive(Clone, PartialEq, Eq, Hash, Debug)]
29pub struct CargoTomlNotFoundError {
30 pub searched_at: PathBuf,
31 pub reason: String,
32}
33
34impl 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
45impl Error for CargoTomlNotFoundError {}
46
47#[derive(Debug, Clone)] 27#[derive(Debug, Clone)]
48pub enum ProjectWorkspace { 28pub enum ProjectWorkspace {
49 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
@@ -77,31 +57,119 @@ impl PackageRoot {
77 } 57 }
78} 58}
79 59
80impl ProjectWorkspace { 60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81 pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { 61pub enum ProjectRoot {
82 ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) 62 ProjectJson(PathBuf),
63 CargoToml(PathBuf),
64}
65
66impl 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())
83 } 75 }
84 76
85 pub fn discover_with_sysroot( 77 pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
86 path: &Path, 78 let mut candidates = ProjectRoot::discover(path)?;
87 with_sysroot: bool, 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
154impl ProjectWorkspace {
155 pub fn load(
156 root: ProjectRoot,
88 cargo_features: &CargoConfig, 157 cargo_features: &CargoConfig,
158 with_sysroot: bool,
89 ) -> Result<ProjectWorkspace> { 159 ) -> Result<ProjectWorkspace> {
90 match find_rust_project_json(path) { 160 let res = match root {
91 Some(json_path) => { 161 ProjectRoot::ProjectJson(project_json) => {
92 let file = File::open(&json_path) 162 let file = File::open(&project_json).with_context(|| {
93 .with_context(|| format!("Failed to open json file {}", json_path.display()))?; 163 format!("Failed to open json file {}", project_json.display())
164 })?;
94 let reader = BufReader::new(file); 165 let reader = BufReader::new(file);
95 Ok(ProjectWorkspace::Json { 166 ProjectWorkspace::Json {
96 project: from_reader(reader).with_context(|| { 167 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())
98 })?, 169 })?,
99 }) 170 }
100 } 171 }
101 None => { 172 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) 173 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
106 .with_context(|| { 174 .with_context(|| {
107 format!( 175 format!(
@@ -119,9 +187,11 @@ impl ProjectWorkspace {
119 } else { 187 } else {
120 Sysroot::default() 188 Sysroot::default()
121 }; 189 };
122 Ok(ProjectWorkspace::Cargo { cargo, sysroot }) 190 ProjectWorkspace::Cargo { cargo, sysroot }
123 } 191 }
124 } 192 };
193
194 Ok(res)
125 } 195 }
126 196
127 /// Returns the roots for the current `ProjectWorkspace` 197 /// Returns the roots for the current `ProjectWorkspace`
@@ -469,87 +539,6 @@ impl ProjectWorkspace {
469 } 539 }
470} 540}
471 541
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 { 542pub fn get_rustc_cfg_options() -> CfgOptions {
554 let mut cfg_options = CfgOptions::default(); 543 let mut cfg_options = CfgOptions::default();
555 544