Skip to content

Commit

Permalink
Merge #3995
Browse files Browse the repository at this point in the history
3995: Separate project discovery from project loading r=matklad a=matklad

bors r+
🤖

Co-authored-by: Aleksey Kladov <[email protected]>
  • Loading branch information
bors[bot] and matklad authored Apr 16, 2020
2 parents 10d8cb9 + 422ae47 commit 8d296be
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 149 deletions.
231 changes: 110 additions & 121 deletions crates/ra_project_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ mod json_project;
mod sysroot;

use std::{
error::Error,
fs::{read_dir, File, ReadDir},
io::BufReader,
io::{self, BufReader},
path::{Path, PathBuf},
process::Command,
};
Expand All @@ -25,25 +24,6 @@ pub use crate::{
};
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)]
pub enum ProjectWorkspace {
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
Expand Down Expand Up @@ -77,31 +57,119 @@ impl PackageRoot {
}
}

impl ProjectWorkspace {
pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> {
ProjectWorkspace::discover_with_sysroot(path, true, cargo_features)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ProjectRoot {
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(
path: &Path,
with_sysroot: bool,
pub fn discover_single(path: &Path) -> Result<ProjectRoot> {
let mut candidates = ProjectRoot::discover(path)?;
let res = match candidates.pop() {
None => bail!("no projects"),
Some(it) => it,
};

if !candidates.is_empty() {
bail!("more than one project")
}
Ok(res)
}

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());
}

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(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> {
match find_rust_project_json(path) {
Some(json_path) => {
let file = File::open(&json_path)
.with_context(|| format!("Failed to open json file {}", json_path.display()))?;
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);
Ok(ProjectWorkspace::Json {
ProjectWorkspace::Json {
project: from_reader(reader).with_context(|| {
format!("Failed to deserialize json file {}", json_path.display())
format!("Failed to deserialize json file {}", project_json.display())
})?,
})
}
}
None => {
let cargo_toml = find_cargo_toml(path).with_context(|| {
format!("Failed to find Cargo.toml for path {}", path.display())
})?;
ProjectRoot::CargoToml(cargo_toml) => {
let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features)
.with_context(|| {
format!(
Expand All @@ -119,9 +187,11 @@ impl ProjectWorkspace {
} else {
Sysroot::default()
};
Ok(ProjectWorkspace::Cargo { cargo, sysroot })
ProjectWorkspace::Cargo { cargo, sysroot }
}
}
};

Ok(res)
}

/// Returns the roots for the current `ProjectWorkspace`
Expand Down Expand Up @@ -469,87 +539,6 @@ impl ProjectWorkspace {
}
}

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 {
let mut cfg_options = CfgOptions::default();

Expand Down
8 changes: 5 additions & 3 deletions crates/rust-analyzer/src/cli/load_cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver};
use ra_db::{ExternSourceId, FileId, SourceRootId};
use ra_ide::{AnalysisChange, AnalysisHost};
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 rustc_hash::{FxHashMap, FxHashSet};
Expand All @@ -28,9 +28,11 @@ pub(crate) fn load_cargo(
with_proc_macro: bool,
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
let root = std::env::current_dir()?.join(root);
let ws = ProjectWorkspace::discover(
root.as_ref(),
let root = ProjectRoot::discover_single(&root)?;
let ws = ProjectWorkspace::load(
root,
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
true,
)?;

let mut extern_dirs = FxHashSet::default();
Expand Down
60 changes: 35 additions & 25 deletions crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::{
};

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

let mut loop_state = LoopState::default();
let mut world_state = {
// FIXME: support dynamic workspace loading.
let workspaces = {
let mut loaded_workspaces = Vec::new();
for ws_root in &ws_roots {
let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot(
ws_root.as_path(),
config.with_sysroot,
&config.cargo,
);
match workspace {
Ok(workspace) => loaded_workspaces.push(workspace),
Err(e) => {
log::error!("loading workspace failed: {:?}", e);

if let Some(ra_project_model::CargoTomlNotFoundError { .. }) =
e.downcast_ref()
{
if !config.notifications.cargo_toml_not_found {
continue;
}
}
// FIXME: support dynamic workspace loading.
let mut visited = FxHashSet::default();
let project_roots = ws_roots
.iter()
.filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok())
.flatten()
.filter(|it| visited.insert(it.clone()))
.collect::<Vec<_>>();

if project_roots.is_empty() && config.notifications.cargo_toml_not_found {
show_message(
req::MessageType::Error,
format!(
"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(
req::MessageType::Error,
format!("rust-analyzer failed to load workspace: {:?}", e),
format!("rust-analyzer failed to load workspace: {:#}", err),
&connection.sender,
);
}
}
}
loaded_workspaces
})
.ok()
})
.collect::<Vec<_>>()
};

let globs = config
Expand Down

0 comments on commit 8d296be

Please sign in to comment.