Merge #3995
3995: Separate project discovery from project loading r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
8d296be109
@ -5,9 +5,8 @@
|
|||||||
mod sysroot;
|
mod sysroot;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
|
||||||
fs::{read_dir, File, ReadDir},
|
fs::{read_dir, File, ReadDir},
|
||||||
io::BufReader,
|
io::{self, BufReader},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
@ -25,25 +24,6 @@
|
|||||||
};
|
};
|
||||||
pub use ra_proc_macro::ProcMacroClient;
|
pub use ra_proc_macro::ProcMacroClient;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub struct CargoTomlNotFoundError {
|
|
||||||
pub searched_at: PathBuf,
|
|
||||||
pub reason: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CargoTomlNotFoundError {
|
|
||||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
fmt,
|
|
||||||
"can't find Cargo.toml at {}, due to {}",
|
|
||||||
self.searched_at.display(),
|
|
||||||
self.reason
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for CargoTomlNotFoundError {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ProjectWorkspace {
|
pub enum ProjectWorkspace {
|
||||||
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
|
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
|
||||||
@ -77,31 +57,119 @@ pub fn is_member(&self) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectWorkspace {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
|
pub enum ProjectRoot {
|
||||||
ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
|
ProjectJson(PathBuf),
|
||||||
|
CargoToml(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectRoot {
|
||||||
|
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> {
|
||||||
|
if path.ends_with("rust-project.json") {
|
||||||
|
return Ok(ProjectRoot::ProjectJson(path));
|
||||||
|
}
|
||||||
|
if path.ends_with("Cargo.toml") {
|
||||||
|
return Ok(ProjectRoot::CargoToml(path));
|
||||||
|
}
|
||||||
|
bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discover_with_sysroot(
|
pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
|
||||||
path: &Path,
|
let mut candidates = ProjectRoot::discover(path)?;
|
||||||
with_sysroot: bool,
|
let res = match candidates.pop() {
|
||||||
cargo_features: &CargoConfig,
|
None => bail!("no projects"),
|
||||||
) -> Result<ProjectWorkspace> {
|
Some(it) => it,
|
||||||
match find_rust_project_json(path) {
|
};
|
||||||
Some(json_path) => {
|
|
||||||
let file = File::open(&json_path)
|
if !candidates.is_empty() {
|
||||||
.with_context(|| format!("Failed to open json file {}", json_path.display()))?;
|
bail!("more than one project")
|
||||||
let reader = BufReader::new(file);
|
}
|
||||||
Ok(ProjectWorkspace::Json {
|
Ok(res)
|
||||||
project: from_reader(reader).with_context(|| {
|
}
|
||||||
format!("Failed to deserialize json file {}", json_path.display())
|
|
||||||
})?,
|
pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> {
|
||||||
})
|
if let Some(project_json) = find_rust_project_json(path) {
|
||||||
|
return Ok(vec![ProjectRoot::ProjectJson(project_json)]);
|
||||||
|
}
|
||||||
|
return find_cargo_toml(path)
|
||||||
|
.map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect());
|
||||||
|
|
||||||
|
fn find_rust_project_json(path: &Path) -> Option<PathBuf> {
|
||||||
|
if path.ends_with("rust-project.json") {
|
||||||
|
return Some(path.to_path_buf());
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
let cargo_toml = find_cargo_toml(path).with_context(|| {
|
let mut curr = Some(path);
|
||||||
format!("Failed to find Cargo.toml for path {}", path.display())
|
while let Some(path) = curr {
|
||||||
|
let candidate = path.join("rust-project.json");
|
||||||
|
if candidate.exists() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
curr = path.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||||
|
if path.ends_with("Cargo.toml") {
|
||||||
|
return Ok(vec![path.to_path_buf()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(p) = find_cargo_toml_in_parent_dir(path) {
|
||||||
|
return Ok(vec![p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entities = read_dir(path)?;
|
||||||
|
Ok(find_cargo_toml_in_child_dir(entities))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> {
|
||||||
|
let mut curr = Some(path);
|
||||||
|
while let Some(path) = curr {
|
||||||
|
let candidate = path.join("Cargo.toml");
|
||||||
|
if candidate.exists() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
curr = path.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
|
||||||
|
// Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
|
||||||
|
let mut valid_canditates = vec![];
|
||||||
|
for entity in entities.filter_map(Result::ok) {
|
||||||
|
let candidate = entity.path().join("Cargo.toml");
|
||||||
|
if candidate.exists() {
|
||||||
|
valid_canditates.push(candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid_canditates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectWorkspace {
|
||||||
|
pub fn load(
|
||||||
|
root: ProjectRoot,
|
||||||
|
cargo_features: &CargoConfig,
|
||||||
|
with_sysroot: bool,
|
||||||
|
) -> Result<ProjectWorkspace> {
|
||||||
|
let res = match root {
|
||||||
|
ProjectRoot::ProjectJson(project_json) => {
|
||||||
|
let file = File::open(&project_json).with_context(|| {
|
||||||
|
format!("Failed to open json file {}", project_json.display())
|
||||||
})?;
|
})?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
ProjectWorkspace::Json {
|
||||||
|
project: from_reader(reader).with_context(|| {
|
||||||
|
format!("Failed to deserialize json file {}", project_json.display())
|
||||||
|
})?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProjectRoot::CargoToml(cargo_toml) => {
|
||||||
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
|
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
@ -119,9 +187,11 @@ pub fn discover_with_sysroot(
|
|||||||
} else {
|
} else {
|
||||||
Sysroot::default()
|
Sysroot::default()
|
||||||
};
|
};
|
||||||
Ok(ProjectWorkspace::Cargo { cargo, sysroot })
|
ProjectWorkspace::Cargo { cargo, sysroot }
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the roots for the current `ProjectWorkspace`
|
/// Returns the roots for the current `ProjectWorkspace`
|
||||||
@ -469,87 +539,6 @@ pub fn workspace_root_for(&self, path: &Path) -> Option<&Path> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_rust_project_json(path: &Path) -> Option<PathBuf> {
|
|
||||||
if path.ends_with("rust-project.json") {
|
|
||||||
return Some(path.to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut curr = Some(path);
|
|
||||||
while let Some(path) = curr {
|
|
||||||
let candidate = path.join("rust-project.json");
|
|
||||||
if candidate.exists() {
|
|
||||||
return Some(candidate);
|
|
||||||
}
|
|
||||||
curr = path.parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> {
|
|
||||||
let mut curr = Some(path);
|
|
||||||
while let Some(path) = curr {
|
|
||||||
let candidate = path.join("Cargo.toml");
|
|
||||||
if candidate.exists() {
|
|
||||||
return Some(candidate);
|
|
||||||
}
|
|
||||||
curr = path.parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> {
|
|
||||||
// Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
|
|
||||||
let mut valid_canditates = vec![];
|
|
||||||
for entity in entities.filter_map(Result::ok) {
|
|
||||||
let candidate = entity.path().join("Cargo.toml");
|
|
||||||
if candidate.exists() {
|
|
||||||
valid_canditates.push(candidate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
valid_canditates
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
|
|
||||||
if path.ends_with("Cargo.toml") {
|
|
||||||
return Ok(path.to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p) = find_cargo_toml_in_parent_dir(path) {
|
|
||||||
return Ok(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
let entities = match read_dir(path) {
|
|
||||||
Ok(entities) => entities,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(CargoTomlNotFoundError {
|
|
||||||
searched_at: path.to_path_buf(),
|
|
||||||
reason: format!("file system error: {}", e),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut valid_canditates = find_cargo_toml_in_child_dir(entities);
|
|
||||||
match valid_canditates.len() {
|
|
||||||
1 => Ok(valid_canditates.remove(0)),
|
|
||||||
0 => Err(CargoTomlNotFoundError {
|
|
||||||
searched_at: path.to_path_buf(),
|
|
||||||
reason: "no Cargo.toml file found".to_string(),
|
|
||||||
}
|
|
||||||
.into()),
|
|
||||||
_ => Err(CargoTomlNotFoundError {
|
|
||||||
searched_at: path.to_path_buf(),
|
|
||||||
reason: format!(
|
|
||||||
"multiple equally valid Cargo.toml files found: {:?}",
|
|
||||||
valid_canditates
|
|
||||||
),
|
|
||||||
}
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rustc_cfg_options() -> CfgOptions {
|
pub fn get_rustc_cfg_options() -> CfgOptions {
|
||||||
let mut cfg_options = CfgOptions::default();
|
let mut cfg_options = CfgOptions::default();
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
use ra_db::{ExternSourceId, FileId, SourceRootId};
|
use ra_db::{ExternSourceId, FileId, SourceRootId};
|
||||||
use ra_ide::{AnalysisChange, AnalysisHost};
|
use ra_ide::{AnalysisChange, AnalysisHost};
|
||||||
use ra_project_model::{
|
use ra_project_model::{
|
||||||
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace,
|
get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace,
|
||||||
};
|
};
|
||||||
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
|
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
@ -28,9 +28,11 @@ pub(crate) fn load_cargo(
|
|||||||
with_proc_macro: bool,
|
with_proc_macro: bool,
|
||||||
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
|
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
|
||||||
let root = std::env::current_dir()?.join(root);
|
let root = std::env::current_dir()?.join(root);
|
||||||
let ws = ProjectWorkspace::discover(
|
let root = ProjectRoot::discover_single(&root)?;
|
||||||
root.as_ref(),
|
let ws = ProjectWorkspace::load(
|
||||||
|
root,
|
||||||
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
|
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
|
||||||
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut extern_dirs = FxHashSet::default();
|
let mut extern_dirs = FxHashSet::default();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
|
use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
|
||||||
|
use itertools::Itertools;
|
||||||
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
|
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
|
NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
|
||||||
@ -88,37 +89,46 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection)
|
|||||||
|
|
||||||
let mut loop_state = LoopState::default();
|
let mut loop_state = LoopState::default();
|
||||||
let mut world_state = {
|
let mut world_state = {
|
||||||
// FIXME: support dynamic workspace loading.
|
|
||||||
let workspaces = {
|
let workspaces = {
|
||||||
let mut loaded_workspaces = Vec::new();
|
// FIXME: support dynamic workspace loading.
|
||||||
for ws_root in &ws_roots {
|
let mut visited = FxHashSet::default();
|
||||||
let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot(
|
let project_roots = ws_roots
|
||||||
ws_root.as_path(),
|
.iter()
|
||||||
config.with_sysroot,
|
.filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
|
||||||
&config.cargo,
|
.flatten()
|
||||||
);
|
.filter(|it| visited.insert(it.clone()))
|
||||||
match workspace {
|
.collect::<Vec<_>>();
|
||||||
Ok(workspace) => loaded_workspaces.push(workspace),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("loading workspace failed: {:?}", e);
|
|
||||||
|
|
||||||
if let Some(ra_project_model::CargoTomlNotFoundError { .. }) =
|
if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
|
||||||
e.downcast_ref()
|
show_message(
|
||||||
{
|
req::MessageType::Error,
|
||||||
if !config.notifications.cargo_toml_not_found {
|
format!(
|
||||||
continue;
|
"rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}",
|
||||||
}
|
ws_roots.iter().format_with(", ", |it, f| f(&it.display()))
|
||||||
}
|
),
|
||||||
|
&connection.sender,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
project_roots
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|root| {
|
||||||
|
ra_project_model::ProjectWorkspace::load(
|
||||||
|
root,
|
||||||
|
&config.cargo,
|
||||||
|
config.with_sysroot,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
log::error!("failed to load workspace: {:#}", err);
|
||||||
show_message(
|
show_message(
|
||||||
req::MessageType::Error,
|
req::MessageType::Error,
|
||||||
format!("rust-analyzer failed to load workspace: {:?}", e),
|
format!("rust-analyzer failed to load workspace: {:#}", err),
|
||||||
&connection.sender,
|
&connection.sender,
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
}
|
.ok()
|
||||||
}
|
})
|
||||||
loaded_workspaces
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
let globs = config
|
let globs = config
|
||||||
|
Loading…
Reference in New Issue
Block a user