aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-04-16 21:02:10 +0100
committerAleksey Kladov <[email protected]>2020-04-16 21:02:10 +0100
commitbe2654b0ed3e4eb51bc3745b2329d6264588549f (patch)
tree6340818f6ddf3b13360f09431c6208cc98786a21 /crates
parentcae2498513601c507bb10b15710feb800a24517f (diff)
Decouple project loading from project discovery a bit
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_project_model/src/lib.rs221
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs62
3 files changed, 163 insertions, 128 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
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 43062ea10..f3f266ee4 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{ExternSourceId, FileId, SourceRootId}; 8use ra_db::{ExternSourceId, FileId, SourceRootId};
9use ra_ide::{AnalysisChange, AnalysisHost}; 9use ra_ide::{AnalysisChange, AnalysisHost};
10use ra_project_model::{ 10use ra_project_model::{
11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, 11 get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
12}; 12};
13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; 13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
14use rustc_hash::{FxHashMap, FxHashSet}; 14use rustc_hash::{FxHashMap, FxHashSet};
@@ -27,9 +27,11 @@ pub(crate) fn load_cargo(
27 load_out_dirs_from_check: bool, 27 load_out_dirs_from_check: bool,
28) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { 28) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
29 let root = std::env::current_dir()?.join(root); 29 let root = std::env::current_dir()?.join(root);
30 let ws = ProjectWorkspace::discover( 30 let root = ProjectRoot::discover(&root)?;
31 root.as_ref(), 31 let ws = ProjectWorkspace::load(
32 root,
32 &CargoConfig { load_out_dirs_from_check, ..Default::default() }, 33 &CargoConfig { load_out_dirs_from_check, ..Default::default() },
34 true,
33 )?; 35 )?;
34 36
35 let mut extern_dirs = FxHashSet::default(); 37 let mut extern_dirs = FxHashSet::default();
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 8d1429196..9c345601c 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -88,37 +88,49 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
88 88
89 let mut loop_state = LoopState::default(); 89 let mut loop_state = LoopState::default();
90 let mut world_state = { 90 let mut world_state = {
91 // FIXME: support dynamic workspace loading.
92 let workspaces = { 91 let workspaces = {
93 let mut loaded_workspaces = Vec::new(); 92 // FIXME: support dynamic workspace loading.
94 for ws_root in &ws_roots { 93 let mut visited = FxHashSet::default();
95 let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( 94 let project_roots = ws_roots
96 ws_root.as_path(), 95 .iter()
97 config.with_sysroot, 96 .map(|it| ra_project_model::ProjectRoot::discover(it))
98 &config.cargo, 97 .filter_map(|dir| {
99 ); 98 dir.map_err(|cargo_toml_not_found| {
100 match workspace { 99 log::error!("discovering workspace failed: {:?}", cargo_toml_not_found);
101 Ok(workspace) => loaded_workspaces.push(workspace), 100
102 Err(e) => { 101 if config.notifications.cargo_toml_not_found {
103 log::error!("loading workspace failed: {:?}", e); 102 show_message(
104 103 req::MessageType::Error,
105 if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = 104 format!(
106 e.downcast_ref() 105 "rust-analyzer failed to discover workspace: {:?}",
107 { 106 cargo_toml_not_found
108 if !config.notifications.cargo_toml_not_found { 107 ),
109 continue; 108 &connection.sender,
110 } 109 );
111 } 110 }
112 111 })
112 .ok()
113 })
114 .filter(|it| visited.insert(it.clone()));
115
116 project_roots
117 .filter_map(|root| {
118 ra_project_model::ProjectWorkspace::load(
119 root,
120 &config.cargo,
121 config.with_sysroot,
122 )
123 .map_err(|err| {
124 log::error!("failed to load workspace: {:#}", err);
113 show_message( 125 show_message(
114 req::MessageType::Error, 126 req::MessageType::Error,
115 format!("rust-analyzer failed to load workspace: {:?}", e), 127 format!("rust-analyzer failed to load workspace: {:#}", err),
116 &connection.sender, 128 &connection.sender,
117 ); 129 );
118 } 130 })
119 } 131 .ok()
120 } 132 })
121 loaded_workspaces 133 .collect::<Vec<_>>()
122 }; 134 };
123 135
124 let globs = config 136 let globs = config