rust-analyzer/crates/project_model/src/lib.rs

133 lines
4.1 KiB
Rust
Raw Normal View History

//! FIXME: write short doc here
mod cargo_workspace;
mod cfg_flag;
2020-06-24 12:57:37 +00:00
mod project_json;
mod sysroot;
mod workspace;
mod rustc_cfg;
2021-01-22 11:11:01 +00:00
mod build_data;
use std::{
fs::{read_dir, ReadDir},
2020-06-24 13:52:07 +00:00
io,
process::Command,
};
use anyhow::{bail, Context, Result};
2020-08-13 08:32:19 +00:00
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashSet;
pub use crate::{
2021-01-28 15:33:02 +00:00
build_data::{BuildDataCollector, BuildDataResult},
cargo_workspace::{
CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target,
TargetData, TargetKind,
},
2020-06-24 13:52:07 +00:00
project_json::{ProjectJson, ProjectJsonData},
sysroot::Sysroot,
workspace::{PackageRoot, ProjectWorkspace},
};
2020-08-13 10:07:28 +00:00
pub use proc_macro_api::ProcMacroClient;
2020-06-03 09:44:51 +00:00
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
2020-06-03 10:05:50 +00:00
pub enum ProjectManifest {
ProjectJson(AbsPathBuf),
CargoToml(AbsPathBuf),
}
2020-06-03 10:05:50 +00:00
impl ProjectManifest {
pub fn from_manifest_file(path: AbsPathBuf) -> Result<ProjectManifest> {
if path.ends_with("rust-project.json") {
2020-06-03 10:05:50 +00:00
return Ok(ProjectManifest::ProjectJson(path));
}
if path.ends_with("Cargo.toml") {
2020-06-03 10:05:50 +00:00
return Ok(ProjectManifest::CargoToml(path));
}
bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
2019-08-19 12:41:18 +00:00
}
pub fn discover_single(path: &AbsPath) -> Result<ProjectManifest> {
2020-06-03 10:05:50 +00:00
let mut candidates = ProjectManifest::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: &AbsPath) -> io::Result<Vec<ProjectManifest>> {
2020-05-08 23:51:59 +00:00
if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
2020-06-03 10:05:50 +00:00
return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
}
return find_cargo_toml(path)
2020-06-03 10:05:50 +00:00
.map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
fn find_cargo_toml(path: &AbsPath) -> io::Result<Vec<AbsPathBuf>> {
2020-05-08 23:51:59 +00:00
match find_in_parent_dirs(path, "Cargo.toml") {
Some(it) => Ok(vec![it]),
None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
}
2020-05-08 23:51:59 +00:00
}
fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<AbsPathBuf> {
2020-05-08 23:51:59 +00:00
if path.ends_with(target_file_name) {
return Some(path.to_path_buf());
}
let mut curr = Some(path);
2020-05-08 23:51:59 +00:00
while let Some(path) = curr {
2020-05-08 23:51:59 +00:00
let candidate = path.join(target_file_name);
if candidate.exists() {
return Some(candidate);
}
curr = path.parent();
}
None
}
fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<AbsPathBuf> {
// Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
2020-05-08 23:51:59 +00:00
entities
.filter_map(Result::ok)
.map(|it| it.path().join("Cargo.toml"))
.filter(|it| it.exists())
.map(AbsPathBuf::assert)
2020-05-08 23:51:59 +00:00
.collect()
}
}
2020-06-03 09:44:51 +00:00
pub fn discover_all(paths: &[impl AsRef<AbsPath>]) -> Vec<ProjectManifest> {
2020-06-03 09:44:51 +00:00
let mut res = paths
.iter()
2020-06-03 10:05:50 +00:00
.filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
2020-06-03 09:44:51 +00:00
.flatten()
.collect::<FxHashSet<_>>()
.into_iter()
.collect::<Vec<_>>();
res.sort();
res
}
}
fn utf8_stdout(mut cmd: Command) -> Result<String> {
2020-05-08 12:54:29 +00:00
let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
if !output.status.success() {
2020-05-08 16:53:53 +00:00
match String::from_utf8(output.stderr) {
Ok(stderr) if !stderr.is_empty() => {
bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr)
}
_ => bail!("{:?} failed, {}", cmd, output.status),
}
2020-05-08 12:54:29 +00:00
}
let stdout = String::from_utf8(output.stdout)?;
Ok(stdout.trim().to_string())
2020-05-08 12:54:29 +00:00
}