Skip to content

Commit 8d296be

Browse files
bors[bot]matklad
andauthored
Merge #3995
3995: Separate project discovery from project loading r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 10d8cb9 + 422ae47 commit 8d296be

File tree

3 files changed

+150
-149
lines changed

3 files changed

+150
-149
lines changed

crates/ra_project_model/src/lib.rs

Lines changed: 110 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ mod json_project;
55
mod sysroot;
66

77
use std::{
8-
error::Error,
98
fs::{read_dir, File, ReadDir},
10-
io::BufReader,
9+
io::{self, BufReader},
1110
path::{Path, PathBuf},
1211
process::Command,
1312
};
@@ -25,25 +24,6 @@ pub use crate::{
2524
};
2625
pub use ra_proc_macro::ProcMacroClient;
2726

28-
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
29-
pub struct CargoTomlNotFoundError {
30-
pub searched_at: PathBuf,
31-
pub reason: String,
32-
}
33-
34-
impl 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-
45-
impl Error for CargoTomlNotFoundError {}
46-
4727
#[derive(Debug, Clone)]
4828
pub enum ProjectWorkspace {
4929
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
@@ -77,31 +57,119 @@ impl PackageRoot {
7757
}
7858
}
7959

80-
impl ProjectWorkspace {
81-
pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
82-
ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
60+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61+
pub enum ProjectRoot {
62+
ProjectJson(PathBuf),
63+
CargoToml(PathBuf),
64+
}
65+
66+
impl 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())
8375
}
8476

85-
pub fn discover_with_sysroot(
86-
path: &Path,
87-
with_sysroot: bool,
77+
pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
78+
let mut candidates = ProjectRoot::discover(path)?;
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+
154+
impl ProjectWorkspace {
155+
pub fn load(
156+
root: ProjectRoot,
88157
cargo_features: &CargoConfig,
158+
with_sysroot: bool,
89159
) -> 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()))?;
160+
let res = match root {
161+
ProjectRoot::ProjectJson(project_json) => {
162+
let file = File::open(&project_json).with_context(|| {
163+
format!("Failed to open json file {}", project_json.display())
164+
})?;
94165
let reader = BufReader::new(file);
95-
Ok(ProjectWorkspace::Json {
166+
ProjectWorkspace::Json {
96167
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())
98169
})?,
99-
})
170+
}
100171
}
101-
None => {
102-
let cargo_toml = find_cargo_toml(path).with_context(|| {
103-
format!("Failed to find Cargo.toml for path {}", path.display())
104-
})?;
172+
ProjectRoot::CargoToml(cargo_toml) => {
105173
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
106174
.with_context(|| {
107175
format!(
@@ -119,9 +187,11 @@ impl ProjectWorkspace {
119187
} else {
120188
Sysroot::default()
121189
};
122-
Ok(ProjectWorkspace::Cargo { cargo, sysroot })
190+
ProjectWorkspace::Cargo { cargo, sysroot }
123191
}
124-
}
192+
};
193+
194+
Ok(res)
125195
}
126196

127197
/// Returns the roots for the current `ProjectWorkspace`
@@ -469,87 +539,6 @@ impl ProjectWorkspace {
469539
}
470540
}
471541

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-
553542
pub fn get_rustc_cfg_options() -> CfgOptions {
554543
let mut cfg_options = CfgOptions::default();
555544

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};
@@ -28,9 +28,11 @@ pub(crate) fn load_cargo(
2828
with_proc_macro: bool,
2929
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
3030
let root = std::env::current_dir()?.join(root);
31-
let ws = ProjectWorkspace::discover(
32-
root.as_ref(),
31+
let root = ProjectRoot::discover_single(&root)?;
32+
let ws = ProjectWorkspace::load(
33+
root,
3334
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
35+
true,
3436
)?;
3537

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

crates/rust-analyzer/src/main_loop.rs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{
1515
};
1616

1717
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18+
use itertools::Itertools;
1819
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
1920
use lsp_types::{
2021
NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
@@ -88,37 +89,46 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
8889

8990
let mut loop_state = LoopState::default();
9091
let mut world_state = {
91-
// FIXME: support dynamic workspace loading.
9292
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-
}
111-
}
93+
// FIXME: support dynamic workspace loading.
94+
let mut visited = FxHashSet::default();
95+
let project_roots = ws_roots
96+
.iter()
97+
.filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
98+
.flatten()
99+
.filter(|it| visited.insert(it.clone()))
100+
.collect::<Vec<_>>();
101+
102+
if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
103+
show_message(
104+
req::MessageType::Error,
105+
format!(
106+
"rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
107+
ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
108+
),
109+
&connection.sender,
110+
);
111+
};
112112

113+
project_roots
114+
.into_iter()
115+
.filter_map(|root| {
116+
ra_project_model::ProjectWorkspace::load(
117+
root,
118+
&config.cargo,
119+
config.with_sysroot,
120+
)
121+
.map_err(|err| {
122+
log::error!("failed to load workspace: {:#}", err);
113123
show_message(
114124
req::MessageType::Error,
115-
format!("rust-analyzer failed to load workspace: {:?}", e),
125+
format!("rust-analyzer failed to load workspace: {:#}", err),
116126
&connection.sender,
117127
);
118-
}
119-
}
120-
}
121-
loaded_workspaces
128+
})
129+
.ok()
130+
})
131+
.collect::<Vec<_>>()
122132
};
123133

124134
let globs = config

0 commit comments

Comments
 (0)