Skip to content

Commit be2654b

Browse files
committed
Decouple project loading from project discovery a bit
1 parent cae2498 commit be2654b

File tree

3 files changed

+163
-128
lines changed

3 files changed

+163
-128
lines changed

crates/ra_project_model/src/lib.rs

Lines changed: 121 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,131 @@ impl PackageRoot {
7777
}
7878
}
7979

80-
impl ProjectWorkspace {
81-
pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
82-
ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
80+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
81+
pub enum ProjectRoot {
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())
8395
}
8496

85-
pub fn discover_with_sysroot(
86-
path: &Path,
87-
with_sysroot: bool,
97+
pub fn discover(path: &Path) -> Result<ProjectRoot, CargoTomlNotFoundError> {
98+
if let Some(project_json) = find_rust_project_json(path) {
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,
88189
cargo_features: &CargoConfig,
190+
with_sysroot: bool,
89191
) -> 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()))?;
192+
let res = match root {
193+
ProjectRoot::ProjectJson(project_json) => {
194+
let file = File::open(&project_json).with_context(|| {
195+
format!("Failed to open json file {}", project_json.display())
196+
})?;
94197
let reader = BufReader::new(file);
95-
Ok(ProjectWorkspace::Json {
198+
ProjectWorkspace::Json {
96199
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())
98201
})?,
99-
})
202+
}
100203
}
101-
None => {
102-
let cargo_toml = find_cargo_toml(path).with_context(|| {
103-
format!("Failed to find Cargo.toml for path {}", path.display())
104-
})?;
204+
ProjectRoot::CargoToml(cargo_toml) => {
105205
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
106206
.with_context(|| {
107207
format!(
@@ -119,9 +219,11 @@ impl ProjectWorkspace {
119219
} else {
120220
Sysroot::default()
121221
};
122-
Ok(ProjectWorkspace::Cargo { cargo, sysroot })
222+
ProjectWorkspace::Cargo { cargo, sysroot }
123223
}
124-
}
224+
};
225+
226+
Ok(res)
125227
}
126228

127229
/// Returns the roots for the current `ProjectWorkspace`
@@ -469,87 +571,6 @@ impl ProjectWorkspace {
469571
}
470572
}
471573

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-
553574
pub fn get_rustc_cfg_options() -> CfgOptions {
554575
let mut cfg_options = CfgOptions::default();
555576

crates/rust-analyzer/src/cli/load_cargo.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver};
88
use ra_db::{ExternSourceId, FileId, SourceRootId};
99
use ra_ide::{AnalysisChange, AnalysisHost};
1010
use ra_project_model::{
11-
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace,
11+
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
1212
};
1313
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
1414
use rustc_hash::{FxHashMap, FxHashSet};
@@ -27,9 +27,11 @@ pub(crate) fn load_cargo(
2727
load_out_dirs_from_check: bool,
2828
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
2929
let root = std::env::current_dir()?.join(root);
30-
let ws = ProjectWorkspace::discover(
31-
root.as_ref(),
30+
let root = ProjectRoot::discover(&root)?;
31+
let ws = ProjectWorkspace::load(
32+
root,
3233
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
34+
true,
3335
)?;
3436

3537
let mut extern_dirs = FxHashSet::default();

crates/rust-analyzer/src/main_loop.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -88,37 +88,49 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
8888

8989
let mut loop_state = LoopState::default();
9090
let mut world_state = {
91-
// FIXME: support dynamic workspace loading.
9291
let workspaces = {
93-
let mut loaded_workspaces = Vec::new();
94-
for ws_root in &ws_roots {
95-
let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot(
96-
ws_root.as_path(),
97-
config.with_sysroot,
98-
&config.cargo,
99-
);
100-
match workspace {
101-
Ok(workspace) => loaded_workspaces.push(workspace),
102-
Err(e) => {
103-
log::error!("loading workspace failed: {:?}", e);
104-
105-
if let Some(ra_project_model::CargoTomlNotFoundError { .. }) =
106-
e.downcast_ref()
107-
{
108-
if !config.notifications.cargo_toml_not_found {
109-
continue;
110-
}
92+
// FIXME: support dynamic workspace loading.
93+
let mut visited = FxHashSet::default();
94+
let project_roots = ws_roots
95+
.iter()
96+
.map(|it| ra_project_model::ProjectRoot::discover(it))
97+
.filter_map(|dir| {
98+
dir.map_err(|cargo_toml_not_found| {
99+
log::error!("discovering workspace failed: {:?}", cargo_toml_not_found);
100+
101+
if config.notifications.cargo_toml_not_found {
102+
show_message(
103+
req::MessageType::Error,
104+
format!(
105+
"rust-analyzer failed to discover workspace: {:?}",
106+
cargo_toml_not_found
107+
),
108+
&connection.sender,
109+
);
111110
}
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);
113125
show_message(
114126
req::MessageType::Error,
115-
format!("rust-analyzer failed to load workspace: {:?}", e),
127+
format!("rust-analyzer failed to load workspace: {:#}", err),
116128
&connection.sender,
117129
);
118-
}
119-
}
120-
}
121-
loaded_workspaces
130+
})
131+
.ok()
132+
})
133+
.collect::<Vec<_>>()
122134
};
123135

124136
let globs = config

0 commit comments

Comments
 (0)