From 00d927a1885ec2938d3365a8e136993445b437f5 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 5 Mar 2019 22:29:23 +0100 Subject: Initial implementation of project-lock.json. This commit adds a initial implementation of project-lock.json, a build system agnostic method of specifying the crate graph and roots. --- crates/ra_project_model/src/json_project.rs | 49 ++++++ crates/ra_project_model/src/lib.rs | 238 ++++++++++++++++++++-------- 2 files changed, 218 insertions(+), 69 deletions(-) create mode 100644 crates/ra_project_model/src/json_project.rs (limited to 'crates/ra_project_model/src') diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs new file mode 100644 index 000000000..9a9eb9e1f --- /dev/null +++ b/crates/ra_project_model/src/json_project.rs @@ -0,0 +1,49 @@ +use std::path::PathBuf; + +use serde::Deserialize; + +/// A root points to the directory which contains Rust crates. rust-analyzer watches all files in +/// all roots. Roots might be nested. +#[derive(Clone, Debug, Deserialize)] +#[serde(transparent)] +pub struct Root { + pub(crate) path: PathBuf, +} + +/// A crate points to the root module of a crate and lists the dependencies of the crate. This is +/// useful in creating the crate graph. +#[derive(Clone, Debug, Deserialize)] +pub struct Crate { + pub(crate) root_module: PathBuf, + pub(crate) edition: Edition, + pub(crate) deps: Vec, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +#[serde(rename = "edition")] +pub enum Edition { + #[serde(rename = "2015")] + Edition2015, + #[serde(rename = "2018")] + Edition2018, +} + +/// Identifies a crate by position in the crates array. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[serde(transparent)] +pub struct CrateId(pub usize); + +/// A dependency of a crate, identified by its id in the crates array and name. +#[derive(Clone, Debug, Deserialize)] +pub struct Dep { + #[serde(rename = "crate")] + pub(crate) krate: CrateId, + pub(crate) name: String, +} + +/// Roots and crates that compose this Rust project. +#[derive(Clone, Debug, Deserialize)] +pub struct JsonProject { + pub(crate) roots: Vec, + pub(crate) crates: Vec, +} diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 1b18ac836..ded222446 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -1,15 +1,23 @@ mod cargo_workspace; +mod json_project; mod sysroot; -use std::path::{Path, PathBuf}; +use std::{ + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; use failure::bail; use rustc_hash::FxHashMap; use ra_db::{CrateGraph, FileId, Edition}; +use serde_json::from_reader; + pub use crate::{ cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, + json_project::JsonProject, sysroot::Sysroot, }; @@ -17,105 +25,197 @@ pub use crate::{ pub type Result = ::std::result::Result; #[derive(Debug, Clone)] -pub struct ProjectWorkspace { - pub cargo: CargoWorkspace, - pub sysroot: Sysroot, +pub enum ProjectWorkspace { + /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. + Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, + /// Project workspace was manually specified using a `rust-project.json` file. + Json { project: JsonProject }, } impl ProjectWorkspace { pub fn discover(path: &Path) -> Result { - let cargo_toml = find_cargo_toml(path)?; - let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?; - let sysroot = Sysroot::discover(&cargo_toml)?; - let res = ProjectWorkspace { cargo, sysroot }; - Ok(res) + match find_rust_project_json(path) { + Some(json_path) => { + let file = File::open(json_path)?; + let reader = BufReader::new(file); + Ok(ProjectWorkspace::Json { project: from_reader(reader)? }) + } + None => { + let cargo_toml = find_cargo_toml(path)?; + Ok(ProjectWorkspace::Cargo { + cargo: CargoWorkspace::from_cargo_metadata(&cargo_toml)?, + sysroot: Sysroot::discover(&cargo_toml)?, + }) + } + } } - pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option) -> CrateGraph { - let mut crate_graph = CrateGraph::default(); - let mut sysroot_crates = FxHashMap::default(); - for krate in self.sysroot.crates() { - if let Some(file_id) = load(krate.root(&self.sysroot)) { - sysroot_crates - .insert(krate, crate_graph.add_crate_root(file_id, Edition::Edition2015)); + pub fn add_roots(&self, roots: &mut Vec) { + match self { + ProjectWorkspace::Json { project } => { + for root in &project.roots { + roots.push(root.path.clone()); + } } - } - for from in self.sysroot.crates() { - for to in from.deps(&self.sysroot) { - let name = to.name(&self.sysroot); - if let (Some(&from), Some(&to)) = - (sysroot_crates.get(&from), sysroot_crates.get(&to)) - { - if let Err(_) = crate_graph.add_dep(from, name.into(), to) { - log::error!("cyclic dependency between sysroot crates") - } + ProjectWorkspace::Cargo { cargo, sysroot } => { + for pkg in cargo.packages() { + roots.push(pkg.root(&cargo).to_path_buf()); + } + for krate in sysroot.crates() { + roots.push(krate.root_dir(&sysroot).to_path_buf()) } } } + } - let libstd = self.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); - - let mut pkg_to_lib_crate = FxHashMap::default(); - let mut pkg_crates = FxHashMap::default(); - // Next, create crates for each package, target pair - for pkg in self.cargo.packages() { - let mut lib_tgt = None; - for tgt in pkg.targets(&self.cargo) { - let root = tgt.root(&self.cargo); - if let Some(file_id) = load(root) { - let edition = pkg.edition(&self.cargo); - let crate_id = crate_graph.add_crate_root(file_id, edition); - if tgt.kind(&self.cargo) == TargetKind::Lib { - lib_tgt = Some(crate_id); - pkg_to_lib_crate.insert(pkg, crate_id); + pub fn count(&self) -> usize { + match self { + ProjectWorkspace::Json { project } => project.crates.len(), + ProjectWorkspace::Cargo { cargo, .. } => cargo.packages().count(), + } + } + + pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option) -> CrateGraph { + let mut crate_graph = CrateGraph::default(); + match self { + ProjectWorkspace::Json { project } => { + let mut crates = FxHashMap::default(); + for (id, krate) in project.crates.iter().enumerate() { + let crate_id = json_project::CrateId(id); + if let Some(file_id) = load(&krate.root_module) { + let edition = match krate.edition { + json_project::Edition::Edition2015 => Edition::Edition2015, + json_project::Edition::Edition2018 => Edition::Edition2018, + }; + crates.insert(crate_id, crate_graph.add_crate_root(file_id, edition)); } - pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); } - } - // Set deps to the std and to the lib target of the current package - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Some(to) = lib_tgt { - if to != from { - if let Err(_) = crate_graph.add_dep(from, pkg.name(&self.cargo).into(), to) + for (id, krate) in project.crates.iter().enumerate() { + for dep in &krate.deps { + let from_crate_id = json_project::CrateId(id); + let to_crate_id = dep.krate; + if let (Some(&from), Some(&to)) = + (crates.get(&from_crate_id), crates.get(&to_crate_id)) { - log::error!( - "cyclic dependency between targets of {}", - pkg.name(&self.cargo) - ) + if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { + log::error!( + "cyclic dependency {:?} -> {:?}", + from_crate_id, + to_crate_id + ); + } } } } - if let Some(std) = libstd { - if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { - log::error!("cyclic dependency on std for {}", pkg.name(&self.cargo)) + } + ProjectWorkspace::Cargo { cargo, sysroot } => { + let mut sysroot_crates = FxHashMap::default(); + for krate in sysroot.crates() { + if let Some(file_id) = load(krate.root(&sysroot)) { + sysroot_crates.insert( + krate, + crate_graph.add_crate_root(file_id, Edition::Edition2015), + ); } } - } - } + for from in sysroot.crates() { + for to in from.deps(&sysroot) { + let name = to.name(&sysroot); + if let (Some(&from), Some(&to)) = + (sysroot_crates.get(&from), sysroot_crates.get(&to)) + { + if let Err(_) = crate_graph.add_dep(from, name.into(), to) { + log::error!("cyclic dependency between sysroot crates") + } + } + } + } + + let libstd = sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it)); + + let mut pkg_to_lib_crate = FxHashMap::default(); + let mut pkg_crates = FxHashMap::default(); + // Next, create crates for each package, target pair + for pkg in cargo.packages() { + let mut lib_tgt = None; + for tgt in pkg.targets(&cargo) { + let root = tgt.root(&cargo); + if let Some(file_id) = load(root) { + let edition = pkg.edition(&cargo); + let crate_id = crate_graph.add_crate_root(file_id, edition); + if tgt.kind(&cargo) == TargetKind::Lib { + lib_tgt = Some(crate_id); + pkg_to_lib_crate.insert(pkg, crate_id); + } + pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); + } + } - // Now add a dep ednge from all targets of upstream to the lib - // target of downstream. - for pkg in self.cargo.packages() { - for dep in pkg.dependencies(&self.cargo) { - if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { + // Set deps to the std and to the lib target of the current package for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { - log::error!( - "cyclic dependency {} -> {}", - pkg.name(&self.cargo), - dep.pkg.name(&self.cargo) - ) + if let Some(to) = lib_tgt { + if to != from { + if let Err(_) = + crate_graph.add_dep(from, pkg.name(&cargo).into(), to) + { + log::error!( + "cyclic dependency between targets of {}", + pkg.name(&cargo) + ) + } + } + } + if let Some(std) = libstd { + if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { + log::error!("cyclic dependency on std for {}", pkg.name(&cargo)) + } + } + } + } + + // Now add a dep ednge from all targets of upstream to the lib + // target of downstream. + for pkg in cargo.packages() { + for dep in pkg.dependencies(&cargo) { + if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { + for &from in pkg_crates.get(&pkg).into_iter().flatten() { + if let Err(_) = + crate_graph.add_dep(from, dep.name.clone().into(), to) + { + log::error!( + "cyclic dependency {} -> {}", + pkg.name(&cargo), + dep.pkg.name(&cargo) + ) + } + } } } } } } - crate_graph } } +fn find_rust_project_json(path: &Path) -> Option { + 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 { if path.ends_with("Cargo.toml") { return Ok(path.to_path_buf()); -- cgit v1.2.3