mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Decouple project loading from project discovery a bit
This commit is contained in:
parent
cae2498513
commit
be2654b0ed
3 changed files with 165 additions and 130 deletions
|
@ -77,31 +77,131 @@ 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),
|
||||
}
|
||||
|
||||
pub fn discover_with_sysroot(
|
||||
path: &Path,
|
||||
with_sysroot: bool,
|
||||
cargo_features: &CargoConfig,
|
||||
) -> 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 reader = BufReader::new(file);
|
||||
Ok(ProjectWorkspace::Json {
|
||||
project: from_reader(reader).with_context(|| {
|
||||
format!("Failed to deserialize json file {}", json_path.display())
|
||||
})?,
|
||||
})
|
||||
impl ProjectRoot {
|
||||
pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> {
|
||||
if path.ends_with("rust-project.json") {
|
||||
return Ok(ProjectRoot::ProjectJson(path));
|
||||
}
|
||||
None => {
|
||||
let cargo_toml = find_cargo_toml(path).with_context(|| {
|
||||
format!("Failed to find Cargo.toml for path {}", path.display())
|
||||
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(path: &Path) -> Result<ProjectRoot, CargoTomlNotFoundError> {
|
||||
if let Some(project_json) = find_rust_project_json(path) {
|
||||
return Ok(ProjectRoot::ProjectJson(project_json));
|
||||
}
|
||||
return find_cargo_toml(path).map(ProjectRoot::CargoToml);
|
||||
|
||||
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) -> Result<PathBuf, CargoTomlNotFoundError> {
|
||||
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);
|
||||
return 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()),
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
|
@ -119,9 +219,11 @@ impl ProjectWorkspace {
|
|||
} else {
|
||||
Sysroot::default()
|
||||
};
|
||||
Ok(ProjectWorkspace::Cargo { cargo, sysroot })
|
||||
}
|
||||
ProjectWorkspace::Cargo { cargo, sysroot }
|
||||
}
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns the roots for the current `ProjectWorkspace`
|
||||
|
@ -469,87 +571,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();
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
@ -27,9 +27,11 @@ pub(crate) fn load_cargo(
|
|||
load_out_dirs_from_check: 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(&root)?;
|
||||
let ws = ProjectWorkspace::load(
|
||||
root,
|
||||
&CargoConfig { load_out_dirs_from_check, ..Default::default() },
|
||||
true,
|
||||
)?;
|
||||
|
||||
let mut extern_dirs = FxHashSet::default();
|
||||
|
|
|
@ -88,37 +88,49 @@ 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()
|
||||
.map(|it| ra_project_model::ProjectRoot::discover(it))
|
||||
.filter_map(|dir| {
|
||||
dir.map_err(|cargo_toml_not_found| {
|
||||
log::error!("discovering workspace failed: {:?}", cargo_toml_not_found);
|
||||
|
||||
if config.notifications.cargo_toml_not_found {
|
||||
show_message(
|
||||
req::MessageType::Error,
|
||||
format!("rust-analyzer failed to load workspace: {:?}", e),
|
||||
format!(
|
||||
"rust-analyzer failed to discover workspace: {:?}",
|
||||
cargo_toml_not_found
|
||||
),
|
||||
&connection.sender,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded_workspaces
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.filter(|it| visited.insert(it.clone()));
|
||||
|
||||
project_roots
|
||||
.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: {:#}", err),
|
||||
&connection.sender,
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let globs = config
|
||||
|
|
Loading…
Reference in a new issue