From eac24d52e672c0a9c118e8969bf1b839c3e7f1f3 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Thu, 13 Aug 2020 12:05:30 +0200 Subject: Rename ra_project_model -> project_model --- Cargo.lock | 40 +- crates/project_model/Cargo.toml | 25 + crates/project_model/src/cargo_workspace.rs | 362 ++++++++++++++ crates/project_model/src/cfg_flag.rs | 51 ++ crates/project_model/src/lib.rs | 544 ++++++++++++++++++++++ crates/project_model/src/project_json.rs | 143 ++++++ crates/project_model/src/sysroot.rs | 173 +++++++ crates/ra_project_model/Cargo.toml | 28 -- crates/ra_project_model/src/cargo_workspace.rs | 362 -------------- crates/ra_project_model/src/cfg_flag.rs | 51 -- crates/ra_project_model/src/lib.rs | 544 ---------------------- crates/ra_project_model/src/project_json.rs | 143 ------ crates/ra_project_model/src/sysroot.rs | 173 ------- crates/rust-analyzer/Cargo.toml | 2 +- crates/rust-analyzer/src/bin/main.rs | 2 +- crates/rust-analyzer/src/cargo_target_spec.rs | 2 +- crates/rust-analyzer/src/cli/load_cargo.rs | 2 +- crates/rust-analyzer/src/config.rs | 2 +- crates/rust-analyzer/src/global_state.rs | 2 +- crates/rust-analyzer/src/handlers.rs | 2 +- crates/rust-analyzer/src/main_loop.rs | 2 +- crates/rust-analyzer/src/reload.rs | 6 +- crates/rust-analyzer/tests/heavy_tests/support.rs | 2 +- xtask/tests/tidy.rs | 2 +- 24 files changed, 1331 insertions(+), 1334 deletions(-) create mode 100644 crates/project_model/Cargo.toml create mode 100644 crates/project_model/src/cargo_workspace.rs create mode 100644 crates/project_model/src/cfg_flag.rs create mode 100644 crates/project_model/src/lib.rs create mode 100644 crates/project_model/src/project_json.rs create mode 100644 crates/project_model/src/sysroot.rs delete mode 100644 crates/ra_project_model/Cargo.toml delete mode 100644 crates/ra_project_model/src/cargo_workspace.rs delete mode 100644 crates/ra_project_model/src/cfg_flag.rs delete mode 100644 crates/ra_project_model/src/lib.rs delete mode 100644 crates/ra_project_model/src/project_json.rs delete mode 100644 crates/ra_project_model/src/sysroot.rs diff --git a/Cargo.lock b/Cargo.lock index 18c979b39..f72d9e022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,6 +938,25 @@ dependencies = [ "perf-event", ] +[[package]] +name = "project_model" +version = "0.0.0" +dependencies = [ + "anyhow", + "arena", + "cargo_metadata", + "cfg", + "log", + "paths", + "ra_db", + "ra_proc_macro", + "rustc-hash", + "serde", + "serde_json", + "stdx", + "toolchain", +] + [[package]] name = "quote" version = "1.0.7" @@ -1134,25 +1153,6 @@ dependencies = [ "tt", ] -[[package]] -name = "ra_project_model" -version = "0.1.0" -dependencies = [ - "anyhow", - "arena", - "cargo_metadata", - "cfg", - "log", - "paths", - "ra_db", - "ra_proc_macro", - "rustc-hash", - "serde", - "serde_json", - "stdx", - "toolchain", -] - [[package]] name = "ra_ssr" version = "0.1.0" @@ -1257,13 +1257,13 @@ dependencies = [ "pico-args", "proc_macro_srv", "profile", + "project_model", "ra_db", "ra_hir", "ra_hir_def", "ra_hir_ty", "ra_ide", "ra_ide_db", - "ra_project_model", "ra_ssr", "rayon", "rustc-hash", diff --git a/crates/project_model/Cargo.toml b/crates/project_model/Cargo.toml new file mode 100644 index 000000000..8d8d09387 --- /dev/null +++ b/crates/project_model/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "project_model" +version = "0.0.0" +license = "MIT OR Apache-2.0" +authors = ["rust-analyzer developers"] +edition = "2018" + +[lib] +doctest = false + +[dependencies] +log = "0.4.8" +rustc-hash = "1.1.0" +cargo_metadata = "0.11.1" +serde = { version = "1.0.106", features = ["derive"] } +serde_json = "1.0.48" +anyhow = "1.0.26" + +arena = { path = "../arena" } +cfg = { path = "../cfg" } +ra_db = { path = "../ra_db" } +toolchain = { path = "../toolchain" } +ra_proc_macro = { path = "../ra_proc_macro" } +paths = { path = "../paths" } +stdx = { path = "../stdx" } diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs new file mode 100644 index 000000000..abf8dca96 --- /dev/null +++ b/crates/project_model/src/cargo_workspace.rs @@ -0,0 +1,362 @@ +//! FIXME: write short doc here + +use std::{ + ffi::OsStr, + ops, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::{Context, Result}; +use arena::{Arena, Idx}; +use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; +use paths::{AbsPath, AbsPathBuf}; +use ra_db::Edition; +use rustc_hash::FxHashMap; + +use crate::cfg_flag::CfgFlag; + +/// `CargoWorkspace` represents the logical structure of, well, a Cargo +/// workspace. It pretty closely mirrors `cargo metadata` output. +/// +/// Note that internally, rust analyzer uses a different structure: +/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, +/// while this knows about `Packages` & `Targets`: purely cargo-related +/// concepts. +/// +/// We use absolute paths here, `cargo metadata` guarantees to always produce +/// abs paths. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct CargoWorkspace { + packages: Arena, + targets: Arena, + workspace_root: AbsPathBuf, +} + +impl ops::Index for CargoWorkspace { + type Output = PackageData; + fn index(&self, index: Package) -> &PackageData { + &self.packages[index] + } +} + +impl ops::Index for CargoWorkspace { + type Output = TargetData; + fn index(&self, index: Target) -> &TargetData { + &self.targets[index] + } +} + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct CargoConfig { + /// Do not activate the `default` feature. + pub no_default_features: bool, + + /// Activate all available features + pub all_features: bool, + + /// List of features to activate. + /// This will be ignored if `cargo_all_features` is true. + pub features: Vec, + + /// Runs cargo check on launch to figure out the correct values of OUT_DIR + pub load_out_dirs_from_check: bool, + + /// rustc target + pub target: Option, +} + +pub type Package = Idx; + +pub type Target = Idx; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PackageData { + pub version: String, + pub name: String, + pub manifest: AbsPathBuf, + pub targets: Vec, + pub is_member: bool, + pub dependencies: Vec, + pub edition: Edition, + pub features: Vec, + pub cfgs: Vec, + pub out_dir: Option, + pub proc_macro_dylib_path: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PackageDependency { + pub pkg: Package, + pub name: String, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct TargetData { + pub package: Package, + pub name: String, + pub root: AbsPathBuf, + pub kind: TargetKind, + pub is_proc_macro: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TargetKind { + Bin, + /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). + Lib, + Example, + Test, + Bench, + Other, +} + +impl TargetKind { + fn new(kinds: &[String]) -> TargetKind { + for kind in kinds { + return match kind.as_str() { + "bin" => TargetKind::Bin, + "test" => TargetKind::Test, + "bench" => TargetKind::Bench, + "example" => TargetKind::Example, + "proc-macro" => TargetKind::Lib, + _ if kind.contains("lib") => TargetKind::Lib, + _ => continue, + }; + } + TargetKind::Other + } +} + +impl PackageData { + pub fn root(&self) -> &AbsPath { + self.manifest.parent().unwrap() + } +} + +impl CargoWorkspace { + pub fn from_cargo_metadata( + cargo_toml: &AbsPath, + cargo_features: &CargoConfig, + ) -> Result { + let mut meta = MetadataCommand::new(); + meta.cargo_path(toolchain::cargo()); + meta.manifest_path(cargo_toml.to_path_buf()); + if cargo_features.all_features { + meta.features(CargoOpt::AllFeatures); + } else { + if cargo_features.no_default_features { + // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` + // https://github.com/oli-obk/cargo_metadata/issues/79 + meta.features(CargoOpt::NoDefaultFeatures); + } + if !cargo_features.features.is_empty() { + meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); + } + } + if let Some(parent) = cargo_toml.parent() { + meta.current_dir(parent.to_path_buf()); + } + if let Some(target) = cargo_features.target.as_ref() { + meta.other_options(vec![String::from("--filter-platform"), target.clone()]); + } + let mut meta = meta.exec().with_context(|| { + format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display()) + })?; + + let mut out_dir_by_id = FxHashMap::default(); + let mut cfgs = FxHashMap::default(); + let mut proc_macro_dylib_paths = FxHashMap::default(); + if cargo_features.load_out_dirs_from_check { + let resources = load_extern_resources(cargo_toml, cargo_features)?; + out_dir_by_id = resources.out_dirs; + cfgs = resources.cfgs; + proc_macro_dylib_paths = resources.proc_dylib_paths; + } + + let mut pkg_by_id = FxHashMap::default(); + let mut packages = Arena::default(); + let mut targets = Arena::default(); + + let ws_members = &meta.workspace_members; + + meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); + for meta_pkg in meta.packages { + let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = + meta_pkg; + let is_member = ws_members.contains(&id); + let edition = edition + .parse::() + .with_context(|| format!("Failed to parse edition {}", edition))?; + let pkg = packages.alloc(PackageData { + name, + version: version.to_string(), + manifest: AbsPathBuf::assert(manifest_path), + targets: Vec::new(), + is_member, + edition, + dependencies: Vec::new(), + features: Vec::new(), + cfgs: cfgs.get(&id).cloned().unwrap_or_default(), + out_dir: out_dir_by_id.get(&id).cloned(), + proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), + }); + let pkg_data = &mut packages[pkg]; + pkg_by_id.insert(id, pkg); + for meta_tgt in meta_pkg.targets { + let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"]; + let tgt = targets.alloc(TargetData { + package: pkg, + name: meta_tgt.name, + root: AbsPathBuf::assert(meta_tgt.src_path.clone()), + kind: TargetKind::new(meta_tgt.kind.as_slice()), + is_proc_macro, + }); + pkg_data.targets.push(tgt); + } + } + let resolve = meta.resolve.expect("metadata executed with deps"); + for mut node in resolve.nodes { + let source = match pkg_by_id.get(&node.id) { + Some(&src) => src, + // FIXME: replace this and a similar branch below with `.unwrap`, once + // https://github.com/rust-lang/cargo/issues/7841 + // is fixed and hits stable (around 1.43-is probably?). + None => { + log::error!("Node id do not match in cargo metadata, ignoring {}", node.id); + continue; + } + }; + node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg)); + for dep_node in node.deps { + let pkg = match pkg_by_id.get(&dep_node.pkg) { + Some(&pkg) => pkg, + None => { + log::error!( + "Dep node id do not match in cargo metadata, ignoring {}", + dep_node.pkg + ); + continue; + } + }; + let dep = PackageDependency { name: dep_node.name, pkg }; + packages[source].dependencies.push(dep); + } + packages[source].features.extend(node.features); + } + + let workspace_root = AbsPathBuf::assert(meta.workspace_root); + Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root }) + } + + pub fn packages<'a>(&'a self) -> impl Iterator + ExactSizeIterator + 'a { + self.packages.iter().map(|(id, _pkg)| id) + } + + pub fn target_by_root(&self, root: &AbsPath) -> Option { + self.packages() + .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root)) + .next() + .copied() + } + + pub fn workspace_root(&self) -> &AbsPath { + &self.workspace_root + } + + pub fn package_flag(&self, package: &PackageData) -> String { + if self.is_unique(&*package.name) { + package.name.clone() + } else { + format!("{}:{}", package.name, package.version) + } + } + + fn is_unique(&self, name: &str) -> bool { + self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 + } +} + +#[derive(Debug, Clone, Default)] +pub struct ExternResources { + out_dirs: FxHashMap, + proc_dylib_paths: FxHashMap, + cfgs: FxHashMap>, +} + +pub fn load_extern_resources( + cargo_toml: &Path, + cargo_features: &CargoConfig, +) -> Result { + let mut cmd = Command::new(toolchain::cargo()); + cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); + if cargo_features.all_features { + cmd.arg("--all-features"); + } else { + if cargo_features.no_default_features { + // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` + // https://github.com/oli-obk/cargo_metadata/issues/79 + cmd.arg("--no-default-features"); + } + if !cargo_features.features.is_empty() { + cmd.arg("--features"); + cmd.arg(cargo_features.features.join(" ")); + } + } + + let output = cmd.output()?; + + let mut res = ExternResources::default(); + + for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) { + if let Ok(message) = message { + match message { + Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { + let cfgs = { + let mut acc = Vec::new(); + for cfg in cfgs { + match cfg.parse::() { + Ok(it) => acc.push(it), + Err(err) => { + anyhow::bail!("invalid cfg from cargo-metadata: {}", err) + } + }; + } + acc + }; + // cargo_metadata crate returns default (empty) path for + // older cargos, which is not absolute, so work around that. + if out_dir != PathBuf::default() { + let out_dir = AbsPathBuf::assert(out_dir); + res.out_dirs.insert(package_id.clone(), out_dir); + res.cfgs.insert(package_id, cfgs); + } + } + Message::CompilerArtifact(message) => { + if message.target.kind.contains(&"proc-macro".to_string()) { + let package_id = message.package_id; + // Skip rmeta file + if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) + { + let filename = AbsPathBuf::assert(filename.clone()); + res.proc_dylib_paths.insert(package_id, filename); + } + } + } + Message::CompilerMessage(_) => (), + Message::Unknown => (), + Message::BuildFinished(_) => {} + Message::TextLine(_) => {} + } + } + } + Ok(res) +} + +// FIXME: File a better way to know if it is a dylib +fn is_dylib(path: &Path) -> bool { + match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { + None => false, + Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), + } +} diff --git a/crates/project_model/src/cfg_flag.rs b/crates/project_model/src/cfg_flag.rs new file mode 100644 index 000000000..e92962cf6 --- /dev/null +++ b/crates/project_model/src/cfg_flag.rs @@ -0,0 +1,51 @@ +//! Parsing of CfgFlags as command line arguments, as in +//! +//! rustc main.rs --cfg foo --cfg 'feature="bar"' +use std::str::FromStr; + +use cfg::CfgOptions; +use stdx::split_once; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum CfgFlag { + Atom(String), + KeyValue { key: String, value: String }, +} + +impl FromStr for CfgFlag { + type Err = String; + fn from_str(s: &str) -> Result { + let res = match split_once(s, '=') { + Some((key, value)) => { + if !(value.starts_with('"') && value.ends_with('"')) { + return Err(format!("Invalid cfg ({:?}), value should be in quotes", s)); + } + let key = key.to_string(); + let value = value[1..value.len() - 1].to_string(); + CfgFlag::KeyValue { key, value } + } + None => CfgFlag::Atom(s.into()), + }; + Ok(res) + } +} + +impl<'de> serde::Deserialize<'de> for CfgFlag { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) + } +} + +impl Extend for CfgOptions { + fn extend>(&mut self, iter: T) { + for cfg_flag in iter { + match cfg_flag { + CfgFlag::Atom(it) => self.insert_atom(it.into()), + CfgFlag::KeyValue { key, value } => self.insert_key_value(key.into(), value.into()), + } + } + } +} diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs new file mode 100644 index 000000000..ee42198f3 --- /dev/null +++ b/crates/project_model/src/lib.rs @@ -0,0 +1,544 @@ +//! FIXME: write short doc here + +mod cargo_workspace; +mod project_json; +mod sysroot; +mod cfg_flag; + +use std::{ + fs::{self, read_dir, ReadDir}, + io, + process::Command, +}; + +use anyhow::{bail, Context, Result}; +use cfg::CfgOptions; +use paths::{AbsPath, AbsPathBuf}; +use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::cfg_flag::CfgFlag; + +pub use crate::{ + cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, + project_json::{ProjectJson, ProjectJsonData}, + sysroot::Sysroot, +}; + +pub use ra_proc_macro::ProcMacroClient; + +#[derive(Debug, Clone, Eq, PartialEq)] +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: ProjectJson }, +} + +/// `PackageRoot` describes a package root folder. +/// Which may be an external dependency, or a member of +/// the current workspace. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct PackageRoot { + /// Is a member of the current workspace + pub is_member: bool, + pub include: Vec, + pub exclude: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum ProjectManifest { + ProjectJson(AbsPathBuf), + CargoToml(AbsPathBuf), +} + +impl ProjectManifest { + pub fn from_manifest_file(path: AbsPathBuf) -> Result { + if path.ends_with("rust-project.json") { + return Ok(ProjectManifest::ProjectJson(path)); + } + if path.ends_with("Cargo.toml") { + return Ok(ProjectManifest::CargoToml(path)); + } + bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) + } + + pub fn discover_single(path: &AbsPath) -> Result { + 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> { + if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { + return Ok(vec![ProjectManifest::ProjectJson(project_json)]); + } + return find_cargo_toml(path) + .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); + + fn find_cargo_toml(path: &AbsPath) -> io::Result> { + match find_in_parent_dirs(path, "Cargo.toml") { + Some(it) => Ok(vec![it]), + None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)), + } + } + + fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option { + if path.ends_with(target_file_name) { + return Some(path.to_path_buf()); + } + + let mut curr = Some(path); + + while let Some(path) = curr { + 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 { + // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects + entities + .filter_map(Result::ok) + .map(|it| it.path().join("Cargo.toml")) + .filter(|it| it.exists()) + .map(AbsPathBuf::assert) + .collect() + } + } + + pub fn discover_all(paths: &[impl AsRef]) -> Vec { + let mut res = paths + .iter() + .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) + .flatten() + .collect::>() + .into_iter() + .collect::>(); + res.sort(); + res + } +} + +impl ProjectWorkspace { + pub fn load( + manifest: ProjectManifest, + cargo_config: &CargoConfig, + with_sysroot: bool, + ) -> Result { + let res = match manifest { + ProjectManifest::ProjectJson(project_json) => { + let file = fs::read_to_string(&project_json).with_context(|| { + format!("Failed to read json file {}", project_json.display()) + })?; + let data = serde_json::from_str(&file).with_context(|| { + format!("Failed to deserialize json file {}", project_json.display()) + })?; + let project_location = project_json.parent().unwrap().to_path_buf(); + let project = ProjectJson::new(&project_location, data); + ProjectWorkspace::Json { project } + } + ProjectManifest::CargoToml(cargo_toml) => { + let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config) + .with_context(|| { + format!( + "Failed to read Cargo metadata from Cargo.toml file {}", + cargo_toml.display() + ) + })?; + let sysroot = if with_sysroot { + Sysroot::discover(&cargo_toml).with_context(|| { + format!( + "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", + cargo_toml.display() + ) + })? + } else { + Sysroot::default() + }; + ProjectWorkspace::Cargo { cargo, sysroot } + } + }; + + Ok(res) + } + + /// Returns the roots for the current `ProjectWorkspace` + /// The return type contains the path and whether or not + /// the root is a member of the current workspace + pub fn to_roots(&self) -> Vec { + match self { + ProjectWorkspace::Json { project } => project + .crates + .iter() + .map(|krate| PackageRoot { + is_member: krate.is_workspace_member, + include: krate.include.clone(), + exclude: krate.exclude.clone(), + }) + .collect::>() + .into_iter() + .collect::>(), + ProjectWorkspace::Cargo { cargo, sysroot } => cargo + .packages() + .map(|pkg| { + let is_member = cargo[pkg].is_member; + let pkg_root = cargo[pkg].root().to_path_buf(); + + let mut include = vec![pkg_root.clone()]; + include.extend(cargo[pkg].out_dir.clone()); + + let mut exclude = vec![pkg_root.join(".git")]; + if is_member { + exclude.push(pkg_root.join("target")); + } else { + exclude.push(pkg_root.join("tests")); + exclude.push(pkg_root.join("examples")); + exclude.push(pkg_root.join("benches")); + } + PackageRoot { is_member, include, exclude } + }) + .chain(sysroot.crates().map(|krate| PackageRoot { + is_member: false, + include: vec![sysroot[krate].root_dir().to_path_buf()], + exclude: Vec::new(), + })) + .collect(), + } + } + + pub fn proc_macro_dylib_paths(&self) -> Vec { + match self { + ProjectWorkspace::Json { project } => project + .crates + .iter() + .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) + .cloned() + .collect(), + ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo + .packages() + .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) + .cloned() + .collect(), + } + } + + pub fn n_packages(&self) -> usize { + match self { + ProjectWorkspace::Json { project, .. } => project.crates.len(), + ProjectWorkspace::Cargo { cargo, sysroot } => { + cargo.packages().len() + sysroot.crates().len() + } + } + } + + pub fn to_crate_graph( + &self, + target: Option<&str>, + proc_macro_client: &ProcMacroClient, + load: &mut dyn FnMut(&AbsPath) -> Option, + ) -> CrateGraph { + let mut crate_graph = CrateGraph::default(); + match self { + ProjectWorkspace::Json { project } => { + let mut cfg_cache: FxHashMap, Vec> = FxHashMap::default(); + let crates: FxHashMap<_, _> = project + .crates + .iter() + .enumerate() + .filter_map(|(seq_index, krate)| { + let file_path = &krate.root_module; + let file_id = load(&file_path)?; + + let env = krate.env.clone().into_iter().collect(); + let proc_macro = krate + .proc_macro_dylib_path + .clone() + .map(|it| proc_macro_client.by_dylib_path(&it)); + + let target = krate.target.as_deref().or(target); + let target_cfgs = cfg_cache + .entry(target) + .or_insert_with(|| get_rustc_cfg_options(target)); + + let mut cfg_options = CfgOptions::default(); + cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); + + // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env + Some(( + CrateId(seq_index as u32), + crate_graph.add_crate_root( + file_id, + krate.edition, + // FIXME json definitions can store the crate name + None, + cfg_options, + env, + proc_macro.unwrap_or_default(), + ), + )) + }) + .collect(); + + for (id, krate) in project.crates.iter().enumerate() { + for dep in &krate.deps { + let from_crate_id = CrateId(id as u32); + let to_crate_id = dep.crate_id; + if let (Some(&from), Some(&to)) = + (crates.get(&from_crate_id), crates.get(&to_crate_id)) + { + if crate_graph.add_dep(from, dep.name.clone(), to).is_err() { + log::error!( + "cyclic dependency {:?} -> {:?}", + from_crate_id, + to_crate_id + ); + } + } + } + } + } + ProjectWorkspace::Cargo { cargo, sysroot } => { + let mut cfg_options = CfgOptions::default(); + cfg_options.extend(get_rustc_cfg_options(target)); + + let sysroot_crates: FxHashMap<_, _> = sysroot + .crates() + .filter_map(|krate| { + let file_id = load(&sysroot[krate].root)?; + + let env = Env::default(); + let proc_macro = vec![]; + let name = sysroot[krate].name.clone(); + let crate_id = crate_graph.add_crate_root( + file_id, + Edition::Edition2018, + Some(name), + cfg_options.clone(), + env, + proc_macro, + ); + Some((krate, crate_id)) + }) + .collect(); + + for from in sysroot.crates() { + for &to in sysroot[from].deps.iter() { + let name = &sysroot[to].name; + if let (Some(&from), Some(&to)) = + (sysroot_crates.get(&from), sysroot_crates.get(&to)) + { + if crate_graph.add_dep(from, CrateName::new(name).unwrap(), to).is_err() + { + log::error!("cyclic dependency between sysroot crates") + } + } + } + } + + let libcore = sysroot.core().and_then(|it| sysroot_crates.get(&it).copied()); + let liballoc = sysroot.alloc().and_then(|it| sysroot_crates.get(&it).copied()); + let libstd = sysroot.std().and_then(|it| sysroot_crates.get(&it).copied()); + let libproc_macro = + sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied()); + + let mut pkg_to_lib_crate = FxHashMap::default(); + let mut pkg_crates = FxHashMap::default(); + + // Add test cfg for non-sysroot crates + cfg_options.insert_atom("test".into()); + cfg_options.insert_atom("debug_assertions".into()); + + // Next, create crates for each package, target pair + for pkg in cargo.packages() { + let mut lib_tgt = None; + for &tgt in cargo[pkg].targets.iter() { + let root = cargo[tgt].root.as_path(); + if let Some(file_id) = load(root) { + let edition = cargo[pkg].edition; + let cfg_options = { + let mut opts = cfg_options.clone(); + for feature in cargo[pkg].features.iter() { + opts.insert_key_value("feature".into(), feature.into()); + } + opts.extend(cargo[pkg].cfgs.iter().cloned()); + opts + }; + let mut env = Env::default(); + if let Some(out_dir) = &cargo[pkg].out_dir { + // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() + if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { + env.set("OUT_DIR", out_dir); + } + } + let proc_macro = cargo[pkg] + .proc_macro_dylib_path + .as_ref() + .map(|it| proc_macro_client.by_dylib_path(&it)) + .unwrap_or_default(); + + let crate_id = crate_graph.add_crate_root( + file_id, + edition, + Some(cargo[pkg].name.clone()), + cfg_options, + env, + proc_macro.clone(), + ); + if cargo[tgt].kind == TargetKind::Lib { + lib_tgt = Some((crate_id, cargo[tgt].name.clone())); + pkg_to_lib_crate.insert(pkg, crate_id); + } + if cargo[tgt].is_proc_macro { + if let Some(proc_macro) = libproc_macro { + if crate_graph + .add_dep( + crate_id, + CrateName::new("proc_macro").unwrap(), + proc_macro, + ) + .is_err() + { + log::error!( + "cyclic dependency on proc_macro for {}", + &cargo[pkg].name + ) + } + } + } + + pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); + } + } + + // Set deps to the core, std and to the lib target of the current package + for &from in pkg_crates.get(&pkg).into_iter().flatten() { + if let Some((to, name)) = lib_tgt.clone() { + if to != from + && crate_graph + .add_dep( + from, + // For root projects with dashes in their name, + // cargo metadata does not do any normalization, + // so we do it ourselves currently + CrateName::normalize_dashes(&name), + to, + ) + .is_err() + { + { + log::error!( + "cyclic dependency between targets of {}", + &cargo[pkg].name + ) + } + } + } + // core is added as a dependency before std in order to + // mimic rustcs dependency order + if let Some(core) = libcore { + if crate_graph + .add_dep(from, CrateName::new("core").unwrap(), core) + .is_err() + { + log::error!("cyclic dependency on core for {}", &cargo[pkg].name) + } + } + if let Some(alloc) = liballoc { + if crate_graph + .add_dep(from, CrateName::new("alloc").unwrap(), alloc) + .is_err() + { + log::error!("cyclic dependency on alloc for {}", &cargo[pkg].name) + } + } + if let Some(std) = libstd { + if crate_graph + .add_dep(from, CrateName::new("std").unwrap(), std) + .is_err() + { + log::error!("cyclic dependency on std for {}", &cargo[pkg].name) + } + } + } + } + + // Now add a dep edge from all targets of upstream to the lib + // target of downstream. + for pkg in cargo.packages() { + for dep in cargo[pkg].dependencies.iter() { + if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { + for &from in pkg_crates.get(&pkg).into_iter().flatten() { + if crate_graph + .add_dep(from, CrateName::new(&dep.name).unwrap(), to) + .is_err() + { + log::error!( + "cyclic dependency {} -> {}", + &cargo[pkg].name, + &cargo[dep.pkg].name + ) + } + } + } + } + } + } + } + crate_graph + } +} + +fn get_rustc_cfg_options(target: Option<&str>) -> Vec { + let mut res = Vec::new(); + + // Some nightly-only cfgs, which are required for stdlib + res.push(CfgFlag::Atom("target_thread_local".into())); + for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() { + for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() { + res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() }); + } + } + + let rustc_cfgs = { + let mut cmd = Command::new(toolchain::rustc()); + cmd.args(&["--print", "cfg", "-O"]); + if let Some(target) = target { + cmd.args(&["--target", target]); + } + utf8_stdout(cmd) + }; + + match rustc_cfgs { + Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), + Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), + } + + res +} + +fn utf8_stdout(mut cmd: Command) -> Result { + let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; + if !output.status.success() { + 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), + } + } + let stdout = String::from_utf8(output.stdout)?; + Ok(stdout) +} diff --git a/crates/project_model/src/project_json.rs b/crates/project_model/src/project_json.rs new file mode 100644 index 000000000..e3f3163f6 --- /dev/null +++ b/crates/project_model/src/project_json.rs @@ -0,0 +1,143 @@ +//! FIXME: write short doc here + +use std::path::PathBuf; + +use paths::{AbsPath, AbsPathBuf}; +use ra_db::{CrateId, CrateName, Dependency, Edition}; +use rustc_hash::FxHashMap; +use serde::{de, Deserialize}; + +use crate::cfg_flag::CfgFlag; + +/// Roots and crates that compose this Rust project. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProjectJson { + pub(crate) crates: Vec, +} + +/// 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, Eq, PartialEq)] +pub struct Crate { + pub(crate) root_module: AbsPathBuf, + pub(crate) edition: Edition, + pub(crate) deps: Vec, + pub(crate) cfg: Vec, + pub(crate) target: Option, + pub(crate) env: FxHashMap, + pub(crate) proc_macro_dylib_path: Option, + pub(crate) is_workspace_member: bool, + pub(crate) include: Vec, + pub(crate) exclude: Vec, +} + +impl ProjectJson { + pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { + ProjectJson { + crates: data + .crates + .into_iter() + .map(|crate_data| { + let is_workspace_member = crate_data.is_workspace_member.unwrap_or_else(|| { + crate_data.root_module.is_relative() + && !crate_data.root_module.starts_with("..") + || crate_data.root_module.starts_with(base) + }); + let root_module = base.join(crate_data.root_module); + let (include, exclude) = match crate_data.source { + Some(src) => { + let absolutize = |dirs: Vec| { + dirs.into_iter().map(|it| base.join(it)).collect::>() + }; + (absolutize(src.include_dirs), absolutize(src.exclude_dirs)) + } + None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()), + }; + + Crate { + root_module, + edition: crate_data.edition.into(), + deps: crate_data + .deps + .into_iter() + .map(|dep_data| Dependency { + crate_id: CrateId(dep_data.krate as u32), + name: dep_data.name, + }) + .collect::>(), + cfg: crate_data.cfg, + target: crate_data.target, + env: crate_data.env, + proc_macro_dylib_path: crate_data + .proc_macro_dylib_path + .map(|it| base.join(it)), + is_workspace_member, + include, + exclude, + } + }) + .collect::>(), + } + } +} + +#[derive(Deserialize)] +pub struct ProjectJsonData { + crates: Vec, +} + +#[derive(Deserialize)] +struct CrateData { + root_module: PathBuf, + edition: EditionData, + deps: Vec, + #[serde(default)] + cfg: Vec, + target: Option, + #[serde(default)] + env: FxHashMap, + proc_macro_dylib_path: Option, + is_workspace_member: Option, + source: Option, +} + +#[derive(Deserialize)] +#[serde(rename = "edition")] +enum EditionData { + #[serde(rename = "2015")] + Edition2015, + #[serde(rename = "2018")] + Edition2018, +} + +impl From for Edition { + fn from(data: EditionData) -> Self { + match data { + EditionData::Edition2015 => Edition::Edition2015, + EditionData::Edition2018 => Edition::Edition2018, + } + } +} + +#[derive(Deserialize)] +struct DepData { + /// Identifies a crate by position in the crates array. + #[serde(rename = "crate")] + krate: usize, + #[serde(deserialize_with = "deserialize_crate_name")] + name: CrateName, +} + +#[derive(Deserialize)] +struct CrateSource { + include_dirs: Vec, + exclude_dirs: Vec, +} + +fn deserialize_crate_name<'de, D>(de: D) -> Result +where + D: de::Deserializer<'de>, +{ + let name = String::deserialize(de)?; + CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err))) +} diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs new file mode 100644 index 000000000..8239797b6 --- /dev/null +++ b/crates/project_model/src/sysroot.rs @@ -0,0 +1,173 @@ +//! FIXME: write short doc here + +use std::{convert::TryFrom, env, ops, path::Path, process::Command}; + +use anyhow::{bail, format_err, Result}; +use arena::{Arena, Idx}; +use paths::{AbsPath, AbsPathBuf}; + +use crate::utf8_stdout; + +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct Sysroot { + crates: Arena, +} + +pub type SysrootCrate = Idx; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SysrootCrateData { + pub name: String, + pub root: AbsPathBuf, + pub deps: Vec, +} + +impl ops::Index for Sysroot { + type Output = SysrootCrateData; + fn index(&self, index: SysrootCrate) -> &SysrootCrateData { + &self.crates[index] + } +} + +impl Sysroot { + pub fn core(&self) -> Option { + self.by_name("core") + } + + pub fn alloc(&self) -> Option { + self.by_name("alloc") + } + + pub fn std(&self) -> Option { + self.by_name("std") + } + + pub fn proc_macro(&self) -> Option { + self.by_name("proc_macro") + } + + pub fn crates<'a>(&'a self) -> impl Iterator + ExactSizeIterator + 'a { + self.crates.iter().map(|(id, _data)| id) + } + + pub fn discover(cargo_toml: &AbsPath) -> Result { + let src = get_or_install_rust_src(cargo_toml)?; + let mut sysroot = Sysroot { crates: Arena::default() }; + for name in SYSROOT_CRATES.trim().lines() { + // FIXME: remove this path when 1.47 comes out + // https://github.com/rust-lang/rust/pull/73265 + let root = src.join(format!("lib{}", name)).join("lib.rs"); + if root.exists() { + sysroot.crates.alloc(SysrootCrateData { + name: name.into(), + root, + deps: Vec::new(), + }); + } else { + let root = src.join(name).join("src/lib.rs"); + if root.exists() { + sysroot.crates.alloc(SysrootCrateData { + name: name.into(), + root, + deps: Vec::new(), + }); + } + } + } + if let Some(std) = sysroot.std() { + for dep in STD_DEPS.trim().lines() { + if let Some(dep) = sysroot.by_name(dep) { + sysroot.crates[std].deps.push(dep) + } + } + } + if let Some(alloc) = sysroot.alloc() { + if let Some(core) = sysroot.core() { + sysroot.crates[alloc].deps.push(core); + } + } + Ok(sysroot) + } + + fn by_name(&self, name: &str) -> Option { + self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id) + } +} + +fn get_or_install_rust_src(cargo_toml: &AbsPath) -> Result { + if let Ok(path) = env::var("RUST_SRC_PATH") { + let path = AbsPathBuf::try_from(path.as_str()) + .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; + return Ok(path); + } + let current_dir = cargo_toml.parent().unwrap(); + let mut rustc = Command::new(toolchain::rustc()); + rustc.current_dir(current_dir).args(&["--print", "sysroot"]); + let stdout = utf8_stdout(rustc)?; + let sysroot_path = AbsPath::assert(Path::new(stdout.trim())); + let mut src = get_rust_src(sysroot_path); + if src.is_none() { + let mut rustup = Command::new(toolchain::rustup()); + rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]); + utf8_stdout(rustup)?; + src = get_rust_src(sysroot_path); + } + match src { + Some(r) => Ok(r), + None => bail!( + "can't load standard library from sysroot\n\ + {}\n\ + (discovered via `rustc --print sysroot`)\n\ + try running `rustup component add rust-src` or set `RUST_SRC_PATH`", + sysroot_path.display(), + ), + } +} + +fn get_rust_src(sysroot_path: &AbsPath) -> Option { + // try the new path first since the old one still exists + let mut src_path = sysroot_path.join("lib/rustlib/src/rust/library"); + if !src_path.exists() { + // FIXME: remove this path when 1.47 comes out + // https://github.com/rust-lang/rust/pull/73265 + src_path = sysroot_path.join("lib/rustlib/src/rust/src"); + } + if src_path.exists() { + Some(src_path) + } else { + None + } +} + +impl SysrootCrateData { + pub fn root_dir(&self) -> &AbsPath { + self.root.parent().unwrap() + } +} + +const SYSROOT_CRATES: &str = " +alloc +core +panic_abort +panic_unwind +proc_macro +profiler_builtins +rtstartup +std +stdarch +term +test +unwind"; + +const STD_DEPS: &str = " +alloc +core +panic_abort +panic_unwind +profiler_builtins +rtstartup +proc_macro +stdarch +term +test +unwind"; diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml deleted file mode 100644 index 52f2d57b3..000000000 --- a/crates/ra_project_model/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -edition = "2018" -name = "ra_project_model" -version = "0.1.0" -authors = ["rust-analyzer developers"] -license = "MIT OR Apache-2.0" - -[lib] -doctest = false - -[dependencies] -log = "0.4.8" -rustc-hash = "1.1.0" - -cargo_metadata = "0.11.1" - -arena = { path = "../arena" } -cfg = { path = "../cfg" } -ra_db = { path = "../ra_db" } -toolchain = { path = "../toolchain" } -ra_proc_macro = { path = "../ra_proc_macro" } -paths = { path = "../paths" } -stdx = { path = "../stdx" } - -serde = { version = "1.0.106", features = ["derive"] } -serde_json = "1.0.48" - -anyhow = "1.0.26" diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs deleted file mode 100644 index abf8dca96..000000000 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ /dev/null @@ -1,362 +0,0 @@ -//! FIXME: write short doc here - -use std::{ - ffi::OsStr, - ops, - path::{Path, PathBuf}, - process::Command, -}; - -use anyhow::{Context, Result}; -use arena::{Arena, Idx}; -use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; -use paths::{AbsPath, AbsPathBuf}; -use ra_db::Edition; -use rustc_hash::FxHashMap; - -use crate::cfg_flag::CfgFlag; - -/// `CargoWorkspace` represents the logical structure of, well, a Cargo -/// workspace. It pretty closely mirrors `cargo metadata` output. -/// -/// Note that internally, rust analyzer uses a different structure: -/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, -/// while this knows about `Packages` & `Targets`: purely cargo-related -/// concepts. -/// -/// We use absolute paths here, `cargo metadata` guarantees to always produce -/// abs paths. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct CargoWorkspace { - packages: Arena, - targets: Arena, - workspace_root: AbsPathBuf, -} - -impl ops::Index for CargoWorkspace { - type Output = PackageData; - fn index(&self, index: Package) -> &PackageData { - &self.packages[index] - } -} - -impl ops::Index for CargoWorkspace { - type Output = TargetData; - fn index(&self, index: Target) -> &TargetData { - &self.targets[index] - } -} - -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct CargoConfig { - /// Do not activate the `default` feature. - pub no_default_features: bool, - - /// Activate all available features - pub all_features: bool, - - /// List of features to activate. - /// This will be ignored if `cargo_all_features` is true. - pub features: Vec, - - /// Runs cargo check on launch to figure out the correct values of OUT_DIR - pub load_out_dirs_from_check: bool, - - /// rustc target - pub target: Option, -} - -pub type Package = Idx; - -pub type Target = Idx; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PackageData { - pub version: String, - pub name: String, - pub manifest: AbsPathBuf, - pub targets: Vec, - pub is_member: bool, - pub dependencies: Vec, - pub edition: Edition, - pub features: Vec, - pub cfgs: Vec, - pub out_dir: Option, - pub proc_macro_dylib_path: Option, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PackageDependency { - pub pkg: Package, - pub name: String, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TargetData { - pub package: Package, - pub name: String, - pub root: AbsPathBuf, - pub kind: TargetKind, - pub is_proc_macro: bool, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TargetKind { - Bin, - /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...). - Lib, - Example, - Test, - Bench, - Other, -} - -impl TargetKind { - fn new(kinds: &[String]) -> TargetKind { - for kind in kinds { - return match kind.as_str() { - "bin" => TargetKind::Bin, - "test" => TargetKind::Test, - "bench" => TargetKind::Bench, - "example" => TargetKind::Example, - "proc-macro" => TargetKind::Lib, - _ if kind.contains("lib") => TargetKind::Lib, - _ => continue, - }; - } - TargetKind::Other - } -} - -impl PackageData { - pub fn root(&self) -> &AbsPath { - self.manifest.parent().unwrap() - } -} - -impl CargoWorkspace { - pub fn from_cargo_metadata( - cargo_toml: &AbsPath, - cargo_features: &CargoConfig, - ) -> Result { - let mut meta = MetadataCommand::new(); - meta.cargo_path(toolchain::cargo()); - meta.manifest_path(cargo_toml.to_path_buf()); - if cargo_features.all_features { - meta.features(CargoOpt::AllFeatures); - } else { - if cargo_features.no_default_features { - // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` - // https://github.com/oli-obk/cargo_metadata/issues/79 - meta.features(CargoOpt::NoDefaultFeatures); - } - if !cargo_features.features.is_empty() { - meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone())); - } - } - if let Some(parent) = cargo_toml.parent() { - meta.current_dir(parent.to_path_buf()); - } - if let Some(target) = cargo_features.target.as_ref() { - meta.other_options(vec![String::from("--filter-platform"), target.clone()]); - } - let mut meta = meta.exec().with_context(|| { - format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display()) - })?; - - let mut out_dir_by_id = FxHashMap::default(); - let mut cfgs = FxHashMap::default(); - let mut proc_macro_dylib_paths = FxHashMap::default(); - if cargo_features.load_out_dirs_from_check { - let resources = load_extern_resources(cargo_toml, cargo_features)?; - out_dir_by_id = resources.out_dirs; - cfgs = resources.cfgs; - proc_macro_dylib_paths = resources.proc_dylib_paths; - } - - let mut pkg_by_id = FxHashMap::default(); - let mut packages = Arena::default(); - let mut targets = Arena::default(); - - let ws_members = &meta.workspace_members; - - meta.packages.sort_by(|a, b| a.id.cmp(&b.id)); - for meta_pkg in meta.packages { - let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = - meta_pkg; - let is_member = ws_members.contains(&id); - let edition = edition - .parse::() - .with_context(|| format!("Failed to parse edition {}", edition))?; - let pkg = packages.alloc(PackageData { - name, - version: version.to_string(), - manifest: AbsPathBuf::assert(manifest_path), - targets: Vec::new(), - is_member, - edition, - dependencies: Vec::new(), - features: Vec::new(), - cfgs: cfgs.get(&id).cloned().unwrap_or_default(), - out_dir: out_dir_by_id.get(&id).cloned(), - proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), - }); - let pkg_data = &mut packages[pkg]; - pkg_by_id.insert(id, pkg); - for meta_tgt in meta_pkg.targets { - let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"]; - let tgt = targets.alloc(TargetData { - package: pkg, - name: meta_tgt.name, - root: AbsPathBuf::assert(meta_tgt.src_path.clone()), - kind: TargetKind::new(meta_tgt.kind.as_slice()), - is_proc_macro, - }); - pkg_data.targets.push(tgt); - } - } - let resolve = meta.resolve.expect("metadata executed with deps"); - for mut node in resolve.nodes { - let source = match pkg_by_id.get(&node.id) { - Some(&src) => src, - // FIXME: replace this and a similar branch below with `.unwrap`, once - // https://github.com/rust-lang/cargo/issues/7841 - // is fixed and hits stable (around 1.43-is probably?). - None => { - log::error!("Node id do not match in cargo metadata, ignoring {}", node.id); - continue; - } - }; - node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg)); - for dep_node in node.deps { - let pkg = match pkg_by_id.get(&dep_node.pkg) { - Some(&pkg) => pkg, - None => { - log::error!( - "Dep node id do not match in cargo metadata, ignoring {}", - dep_node.pkg - ); - continue; - } - }; - let dep = PackageDependency { name: dep_node.name, pkg }; - packages[source].dependencies.push(dep); - } - packages[source].features.extend(node.features); - } - - let workspace_root = AbsPathBuf::assert(meta.workspace_root); - Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root }) - } - - pub fn packages<'a>(&'a self) -> impl Iterator + ExactSizeIterator + 'a { - self.packages.iter().map(|(id, _pkg)| id) - } - - pub fn target_by_root(&self, root: &AbsPath) -> Option { - self.packages() - .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root)) - .next() - .copied() - } - - pub fn workspace_root(&self) -> &AbsPath { - &self.workspace_root - } - - pub fn package_flag(&self, package: &PackageData) -> String { - if self.is_unique(&*package.name) { - package.name.clone() - } else { - format!("{}:{}", package.name, package.version) - } - } - - fn is_unique(&self, name: &str) -> bool { - self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 - } -} - -#[derive(Debug, Clone, Default)] -pub struct ExternResources { - out_dirs: FxHashMap, - proc_dylib_paths: FxHashMap, - cfgs: FxHashMap>, -} - -pub fn load_extern_resources( - cargo_toml: &Path, - cargo_features: &CargoConfig, -) -> Result { - let mut cmd = Command::new(toolchain::cargo()); - cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); - if cargo_features.all_features { - cmd.arg("--all-features"); - } else { - if cargo_features.no_default_features { - // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` - // https://github.com/oli-obk/cargo_metadata/issues/79 - cmd.arg("--no-default-features"); - } - if !cargo_features.features.is_empty() { - cmd.arg("--features"); - cmd.arg(cargo_features.features.join(" ")); - } - } - - let output = cmd.output()?; - - let mut res = ExternResources::default(); - - for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) { - if let Ok(message) = message { - match message { - Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { - let cfgs = { - let mut acc = Vec::new(); - for cfg in cfgs { - match cfg.parse::() { - Ok(it) => acc.push(it), - Err(err) => { - anyhow::bail!("invalid cfg from cargo-metadata: {}", err) - } - }; - } - acc - }; - // cargo_metadata crate returns default (empty) path for - // older cargos, which is not absolute, so work around that. - if out_dir != PathBuf::default() { - let out_dir = AbsPathBuf::assert(out_dir); - res.out_dirs.insert(package_id.clone(), out_dir); - res.cfgs.insert(package_id, cfgs); - } - } - Message::CompilerArtifact(message) => { - if message.target.kind.contains(&"proc-macro".to_string()) { - let package_id = message.package_id; - // Skip rmeta file - if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) - { - let filename = AbsPathBuf::assert(filename.clone()); - res.proc_dylib_paths.insert(package_id, filename); - } - } - } - Message::CompilerMessage(_) => (), - Message::Unknown => (), - Message::BuildFinished(_) => {} - Message::TextLine(_) => {} - } - } - } - Ok(res) -} - -// FIXME: File a better way to know if it is a dylib -fn is_dylib(path: &Path) -> bool { - match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { - None => false, - Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), - } -} diff --git a/crates/ra_project_model/src/cfg_flag.rs b/crates/ra_project_model/src/cfg_flag.rs deleted file mode 100644 index e92962cf6..000000000 --- a/crates/ra_project_model/src/cfg_flag.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Parsing of CfgFlags as command line arguments, as in -//! -//! rustc main.rs --cfg foo --cfg 'feature="bar"' -use std::str::FromStr; - -use cfg::CfgOptions; -use stdx::split_once; - -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum CfgFlag { - Atom(String), - KeyValue { key: String, value: String }, -} - -impl FromStr for CfgFlag { - type Err = String; - fn from_str(s: &str) -> Result { - let res = match split_once(s, '=') { - Some((key, value)) => { - if !(value.starts_with('"') && value.ends_with('"')) { - return Err(format!("Invalid cfg ({:?}), value should be in quotes", s)); - } - let key = key.to_string(); - let value = value[1..value.len() - 1].to_string(); - CfgFlag::KeyValue { key, value } - } - None => CfgFlag::Atom(s.into()), - }; - Ok(res) - } -} - -impl<'de> serde::Deserialize<'de> for CfgFlag { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) - } -} - -impl Extend for CfgOptions { - fn extend>(&mut self, iter: T) { - for cfg_flag in iter { - match cfg_flag { - CfgFlag::Atom(it) => self.insert_atom(it.into()), - CfgFlag::KeyValue { key, value } => self.insert_key_value(key.into(), value.into()), - } - } - } -} diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs deleted file mode 100644 index ee42198f3..000000000 --- a/crates/ra_project_model/src/lib.rs +++ /dev/null @@ -1,544 +0,0 @@ -//! FIXME: write short doc here - -mod cargo_workspace; -mod project_json; -mod sysroot; -mod cfg_flag; - -use std::{ - fs::{self, read_dir, ReadDir}, - io, - process::Command, -}; - -use anyhow::{bail, Context, Result}; -use cfg::CfgOptions; -use paths::{AbsPath, AbsPathBuf}; -use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use crate::cfg_flag::CfgFlag; - -pub use crate::{ - cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, - project_json::{ProjectJson, ProjectJsonData}, - sysroot::Sysroot, -}; - -pub use ra_proc_macro::ProcMacroClient; - -#[derive(Debug, Clone, Eq, PartialEq)] -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: ProjectJson }, -} - -/// `PackageRoot` describes a package root folder. -/// Which may be an external dependency, or a member of -/// the current workspace. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct PackageRoot { - /// Is a member of the current workspace - pub is_member: bool, - pub include: Vec, - pub exclude: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub enum ProjectManifest { - ProjectJson(AbsPathBuf), - CargoToml(AbsPathBuf), -} - -impl ProjectManifest { - pub fn from_manifest_file(path: AbsPathBuf) -> Result { - if path.ends_with("rust-project.json") { - return Ok(ProjectManifest::ProjectJson(path)); - } - if path.ends_with("Cargo.toml") { - return Ok(ProjectManifest::CargoToml(path)); - } - bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) - } - - pub fn discover_single(path: &AbsPath) -> Result { - 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> { - if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { - return Ok(vec![ProjectManifest::ProjectJson(project_json)]); - } - return find_cargo_toml(path) - .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); - - fn find_cargo_toml(path: &AbsPath) -> io::Result> { - match find_in_parent_dirs(path, "Cargo.toml") { - Some(it) => Ok(vec![it]), - None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)), - } - } - - fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option { - if path.ends_with(target_file_name) { - return Some(path.to_path_buf()); - } - - let mut curr = Some(path); - - while let Some(path) = curr { - 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 { - // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects - entities - .filter_map(Result::ok) - .map(|it| it.path().join("Cargo.toml")) - .filter(|it| it.exists()) - .map(AbsPathBuf::assert) - .collect() - } - } - - pub fn discover_all(paths: &[impl AsRef]) -> Vec { - let mut res = paths - .iter() - .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) - .flatten() - .collect::>() - .into_iter() - .collect::>(); - res.sort(); - res - } -} - -impl ProjectWorkspace { - pub fn load( - manifest: ProjectManifest, - cargo_config: &CargoConfig, - with_sysroot: bool, - ) -> Result { - let res = match manifest { - ProjectManifest::ProjectJson(project_json) => { - let file = fs::read_to_string(&project_json).with_context(|| { - format!("Failed to read json file {}", project_json.display()) - })?; - let data = serde_json::from_str(&file).with_context(|| { - format!("Failed to deserialize json file {}", project_json.display()) - })?; - let project_location = project_json.parent().unwrap().to_path_buf(); - let project = ProjectJson::new(&project_location, data); - ProjectWorkspace::Json { project } - } - ProjectManifest::CargoToml(cargo_toml) => { - let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_config) - .with_context(|| { - format!( - "Failed to read Cargo metadata from Cargo.toml file {}", - cargo_toml.display() - ) - })?; - let sysroot = if with_sysroot { - Sysroot::discover(&cargo_toml).with_context(|| { - format!( - "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", - cargo_toml.display() - ) - })? - } else { - Sysroot::default() - }; - ProjectWorkspace::Cargo { cargo, sysroot } - } - }; - - Ok(res) - } - - /// Returns the roots for the current `ProjectWorkspace` - /// The return type contains the path and whether or not - /// the root is a member of the current workspace - pub fn to_roots(&self) -> Vec { - match self { - ProjectWorkspace::Json { project } => project - .crates - .iter() - .map(|krate| PackageRoot { - is_member: krate.is_workspace_member, - include: krate.include.clone(), - exclude: krate.exclude.clone(), - }) - .collect::>() - .into_iter() - .collect::>(), - ProjectWorkspace::Cargo { cargo, sysroot } => cargo - .packages() - .map(|pkg| { - let is_member = cargo[pkg].is_member; - let pkg_root = cargo[pkg].root().to_path_buf(); - - let mut include = vec![pkg_root.clone()]; - include.extend(cargo[pkg].out_dir.clone()); - - let mut exclude = vec![pkg_root.join(".git")]; - if is_member { - exclude.push(pkg_root.join("target")); - } else { - exclude.push(pkg_root.join("tests")); - exclude.push(pkg_root.join("examples")); - exclude.push(pkg_root.join("benches")); - } - PackageRoot { is_member, include, exclude } - }) - .chain(sysroot.crates().map(|krate| PackageRoot { - is_member: false, - include: vec![sysroot[krate].root_dir().to_path_buf()], - exclude: Vec::new(), - })) - .collect(), - } - } - - pub fn proc_macro_dylib_paths(&self) -> Vec { - match self { - ProjectWorkspace::Json { project } => project - .crates - .iter() - .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) - .cloned() - .collect(), - ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo - .packages() - .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) - .cloned() - .collect(), - } - } - - pub fn n_packages(&self) -> usize { - match self { - ProjectWorkspace::Json { project, .. } => project.crates.len(), - ProjectWorkspace::Cargo { cargo, sysroot } => { - cargo.packages().len() + sysroot.crates().len() - } - } - } - - pub fn to_crate_graph( - &self, - target: Option<&str>, - proc_macro_client: &ProcMacroClient, - load: &mut dyn FnMut(&AbsPath) -> Option, - ) -> CrateGraph { - let mut crate_graph = CrateGraph::default(); - match self { - ProjectWorkspace::Json { project } => { - let mut cfg_cache: FxHashMap, Vec> = FxHashMap::default(); - let crates: FxHashMap<_, _> = project - .crates - .iter() - .enumerate() - .filter_map(|(seq_index, krate)| { - let file_path = &krate.root_module; - let file_id = load(&file_path)?; - - let env = krate.env.clone().into_iter().collect(); - let proc_macro = krate - .proc_macro_dylib_path - .clone() - .map(|it| proc_macro_client.by_dylib_path(&it)); - - let target = krate.target.as_deref().or(target); - let target_cfgs = cfg_cache - .entry(target) - .or_insert_with(|| get_rustc_cfg_options(target)); - - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned()); - - // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env - Some(( - CrateId(seq_index as u32), - crate_graph.add_crate_root( - file_id, - krate.edition, - // FIXME json definitions can store the crate name - None, - cfg_options, - env, - proc_macro.unwrap_or_default(), - ), - )) - }) - .collect(); - - for (id, krate) in project.crates.iter().enumerate() { - for dep in &krate.deps { - let from_crate_id = CrateId(id as u32); - let to_crate_id = dep.crate_id; - if let (Some(&from), Some(&to)) = - (crates.get(&from_crate_id), crates.get(&to_crate_id)) - { - if crate_graph.add_dep(from, dep.name.clone(), to).is_err() { - log::error!( - "cyclic dependency {:?} -> {:?}", - from_crate_id, - to_crate_id - ); - } - } - } - } - } - ProjectWorkspace::Cargo { cargo, sysroot } => { - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(get_rustc_cfg_options(target)); - - let sysroot_crates: FxHashMap<_, _> = sysroot - .crates() - .filter_map(|krate| { - let file_id = load(&sysroot[krate].root)?; - - let env = Env::default(); - let proc_macro = vec![]; - let name = sysroot[krate].name.clone(); - let crate_id = crate_graph.add_crate_root( - file_id, - Edition::Edition2018, - Some(name), - cfg_options.clone(), - env, - proc_macro, - ); - Some((krate, crate_id)) - }) - .collect(); - - for from in sysroot.crates() { - for &to in sysroot[from].deps.iter() { - let name = &sysroot[to].name; - if let (Some(&from), Some(&to)) = - (sysroot_crates.get(&from), sysroot_crates.get(&to)) - { - if crate_graph.add_dep(from, CrateName::new(name).unwrap(), to).is_err() - { - log::error!("cyclic dependency between sysroot crates") - } - } - } - } - - let libcore = sysroot.core().and_then(|it| sysroot_crates.get(&it).copied()); - let liballoc = sysroot.alloc().and_then(|it| sysroot_crates.get(&it).copied()); - let libstd = sysroot.std().and_then(|it| sysroot_crates.get(&it).copied()); - let libproc_macro = - sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied()); - - let mut pkg_to_lib_crate = FxHashMap::default(); - let mut pkg_crates = FxHashMap::default(); - - // Add test cfg for non-sysroot crates - cfg_options.insert_atom("test".into()); - cfg_options.insert_atom("debug_assertions".into()); - - // Next, create crates for each package, target pair - for pkg in cargo.packages() { - let mut lib_tgt = None; - for &tgt in cargo[pkg].targets.iter() { - let root = cargo[tgt].root.as_path(); - if let Some(file_id) = load(root) { - let edition = cargo[pkg].edition; - let cfg_options = { - let mut opts = cfg_options.clone(); - for feature in cargo[pkg].features.iter() { - opts.insert_key_value("feature".into(), feature.into()); - } - opts.extend(cargo[pkg].cfgs.iter().cloned()); - opts - }; - let mut env = Env::default(); - if let Some(out_dir) = &cargo[pkg].out_dir { - // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() - if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { - env.set("OUT_DIR", out_dir); - } - } - let proc_macro = cargo[pkg] - .proc_macro_dylib_path - .as_ref() - .map(|it| proc_macro_client.by_dylib_path(&it)) - .unwrap_or_default(); - - let crate_id = crate_graph.add_crate_root( - file_id, - edition, - Some(cargo[pkg].name.clone()), - cfg_options, - env, - proc_macro.clone(), - ); - if cargo[tgt].kind == TargetKind::Lib { - lib_tgt = Some((crate_id, cargo[tgt].name.clone())); - pkg_to_lib_crate.insert(pkg, crate_id); - } - if cargo[tgt].is_proc_macro { - if let Some(proc_macro) = libproc_macro { - if crate_graph - .add_dep( - crate_id, - CrateName::new("proc_macro").unwrap(), - proc_macro, - ) - .is_err() - { - log::error!( - "cyclic dependency on proc_macro for {}", - &cargo[pkg].name - ) - } - } - } - - pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); - } - } - - // Set deps to the core, std and to the lib target of the current package - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if let Some((to, name)) = lib_tgt.clone() { - if to != from - && crate_graph - .add_dep( - from, - // For root projects with dashes in their name, - // cargo metadata does not do any normalization, - // so we do it ourselves currently - CrateName::normalize_dashes(&name), - to, - ) - .is_err() - { - { - log::error!( - "cyclic dependency between targets of {}", - &cargo[pkg].name - ) - } - } - } - // core is added as a dependency before std in order to - // mimic rustcs dependency order - if let Some(core) = libcore { - if crate_graph - .add_dep(from, CrateName::new("core").unwrap(), core) - .is_err() - { - log::error!("cyclic dependency on core for {}", &cargo[pkg].name) - } - } - if let Some(alloc) = liballoc { - if crate_graph - .add_dep(from, CrateName::new("alloc").unwrap(), alloc) - .is_err() - { - log::error!("cyclic dependency on alloc for {}", &cargo[pkg].name) - } - } - if let Some(std) = libstd { - if crate_graph - .add_dep(from, CrateName::new("std").unwrap(), std) - .is_err() - { - log::error!("cyclic dependency on std for {}", &cargo[pkg].name) - } - } - } - } - - // Now add a dep edge from all targets of upstream to the lib - // target of downstream. - for pkg in cargo.packages() { - for dep in cargo[pkg].dependencies.iter() { - if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { - for &from in pkg_crates.get(&pkg).into_iter().flatten() { - if crate_graph - .add_dep(from, CrateName::new(&dep.name).unwrap(), to) - .is_err() - { - log::error!( - "cyclic dependency {} -> {}", - &cargo[pkg].name, - &cargo[dep.pkg].name - ) - } - } - } - } - } - } - } - crate_graph - } -} - -fn get_rustc_cfg_options(target: Option<&str>) -> Vec { - let mut res = Vec::new(); - - // Some nightly-only cfgs, which are required for stdlib - res.push(CfgFlag::Atom("target_thread_local".into())); - for &ty in ["8", "16", "32", "64", "cas", "ptr"].iter() { - for &key in ["target_has_atomic", "target_has_atomic_load_store"].iter() { - res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() }); - } - } - - let rustc_cfgs = { - let mut cmd = Command::new(toolchain::rustc()); - cmd.args(&["--print", "cfg", "-O"]); - if let Some(target) = target { - cmd.args(&["--target", target]); - } - utf8_stdout(cmd) - }; - - match rustc_cfgs { - Ok(rustc_cfgs) => res.extend(rustc_cfgs.lines().map(|it| it.parse().unwrap())), - Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), - } - - res -} - -fn utf8_stdout(mut cmd: Command) -> Result { - let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; - if !output.status.success() { - 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), - } - } - let stdout = String::from_utf8(output.stdout)?; - Ok(stdout) -} diff --git a/crates/ra_project_model/src/project_json.rs b/crates/ra_project_model/src/project_json.rs deleted file mode 100644 index e3f3163f6..000000000 --- a/crates/ra_project_model/src/project_json.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! FIXME: write short doc here - -use std::path::PathBuf; - -use paths::{AbsPath, AbsPathBuf}; -use ra_db::{CrateId, CrateName, Dependency, Edition}; -use rustc_hash::FxHashMap; -use serde::{de, Deserialize}; - -use crate::cfg_flag::CfgFlag; - -/// Roots and crates that compose this Rust project. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectJson { - pub(crate) crates: Vec, -} - -/// 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, Eq, PartialEq)] -pub struct Crate { - pub(crate) root_module: AbsPathBuf, - pub(crate) edition: Edition, - pub(crate) deps: Vec, - pub(crate) cfg: Vec, - pub(crate) target: Option, - pub(crate) env: FxHashMap, - pub(crate) proc_macro_dylib_path: Option, - pub(crate) is_workspace_member: bool, - pub(crate) include: Vec, - pub(crate) exclude: Vec, -} - -impl ProjectJson { - pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson { - ProjectJson { - crates: data - .crates - .into_iter() - .map(|crate_data| { - let is_workspace_member = crate_data.is_workspace_member.unwrap_or_else(|| { - crate_data.root_module.is_relative() - && !crate_data.root_module.starts_with("..") - || crate_data.root_module.starts_with(base) - }); - let root_module = base.join(crate_data.root_module); - let (include, exclude) = match crate_data.source { - Some(src) => { - let absolutize = |dirs: Vec| { - dirs.into_iter().map(|it| base.join(it)).collect::>() - }; - (absolutize(src.include_dirs), absolutize(src.exclude_dirs)) - } - None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()), - }; - - Crate { - root_module, - edition: crate_data.edition.into(), - deps: crate_data - .deps - .into_iter() - .map(|dep_data| Dependency { - crate_id: CrateId(dep_data.krate as u32), - name: dep_data.name, - }) - .collect::>(), - cfg: crate_data.cfg, - target: crate_data.target, - env: crate_data.env, - proc_macro_dylib_path: crate_data - .proc_macro_dylib_path - .map(|it| base.join(it)), - is_workspace_member, - include, - exclude, - } - }) - .collect::>(), - } - } -} - -#[derive(Deserialize)] -pub struct ProjectJsonData { - crates: Vec, -} - -#[derive(Deserialize)] -struct CrateData { - root_module: PathBuf, - edition: EditionData, - deps: Vec, - #[serde(default)] - cfg: Vec, - target: Option, - #[serde(default)] - env: FxHashMap, - proc_macro_dylib_path: Option, - is_workspace_member: Option, - source: Option, -} - -#[derive(Deserialize)] -#[serde(rename = "edition")] -enum EditionData { - #[serde(rename = "2015")] - Edition2015, - #[serde(rename = "2018")] - Edition2018, -} - -impl From for Edition { - fn from(data: EditionData) -> Self { - match data { - EditionData::Edition2015 => Edition::Edition2015, - EditionData::Edition2018 => Edition::Edition2018, - } - } -} - -#[derive(Deserialize)] -struct DepData { - /// Identifies a crate by position in the crates array. - #[serde(rename = "crate")] - krate: usize, - #[serde(deserialize_with = "deserialize_crate_name")] - name: CrateName, -} - -#[derive(Deserialize)] -struct CrateSource { - include_dirs: Vec, - exclude_dirs: Vec, -} - -fn deserialize_crate_name<'de, D>(de: D) -> Result -where - D: de::Deserializer<'de>, -{ - let name = String::deserialize(de)?; - CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err))) -} diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs deleted file mode 100644 index 8239797b6..000000000 --- a/crates/ra_project_model/src/sysroot.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! FIXME: write short doc here - -use std::{convert::TryFrom, env, ops, path::Path, process::Command}; - -use anyhow::{bail, format_err, Result}; -use arena::{Arena, Idx}; -use paths::{AbsPath, AbsPathBuf}; - -use crate::utf8_stdout; - -#[derive(Default, Debug, Clone, Eq, PartialEq)] -pub struct Sysroot { - crates: Arena, -} - -pub type SysrootCrate = Idx; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SysrootCrateData { - pub name: String, - pub root: AbsPathBuf, - pub deps: Vec, -} - -impl ops::Index for Sysroot { - type Output = SysrootCrateData; - fn index(&self, index: SysrootCrate) -> &SysrootCrateData { - &self.crates[index] - } -} - -impl Sysroot { - pub fn core(&self) -> Option { - self.by_name("core") - } - - pub fn alloc(&self) -> Option { - self.by_name("alloc") - } - - pub fn std(&self) -> Option { - self.by_name("std") - } - - pub fn proc_macro(&self) -> Option { - self.by_name("proc_macro") - } - - pub fn crates<'a>(&'a self) -> impl Iterator + ExactSizeIterator + 'a { - self.crates.iter().map(|(id, _data)| id) - } - - pub fn discover(cargo_toml: &AbsPath) -> Result { - let src = get_or_install_rust_src(cargo_toml)?; - let mut sysroot = Sysroot { crates: Arena::default() }; - for name in SYSROOT_CRATES.trim().lines() { - // FIXME: remove this path when 1.47 comes out - // https://github.com/rust-lang/rust/pull/73265 - let root = src.join(format!("lib{}", name)).join("lib.rs"); - if root.exists() { - sysroot.crates.alloc(SysrootCrateData { - name: name.into(), - root, - deps: Vec::new(), - }); - } else { - let root = src.join(name).join("src/lib.rs"); - if root.exists() { - sysroot.crates.alloc(SysrootCrateData { - name: name.into(), - root, - deps: Vec::new(), - }); - } - } - } - if let Some(std) = sysroot.std() { - for dep in STD_DEPS.trim().lines() { - if let Some(dep) = sysroot.by_name(dep) { - sysroot.crates[std].deps.push(dep) - } - } - } - if let Some(alloc) = sysroot.alloc() { - if let Some(core) = sysroot.core() { - sysroot.crates[alloc].deps.push(core); - } - } - Ok(sysroot) - } - - fn by_name(&self, name: &str) -> Option { - self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id) - } -} - -fn get_or_install_rust_src(cargo_toml: &AbsPath) -> Result { - if let Ok(path) = env::var("RUST_SRC_PATH") { - let path = AbsPathBuf::try_from(path.as_str()) - .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; - return Ok(path); - } - let current_dir = cargo_toml.parent().unwrap(); - let mut rustc = Command::new(toolchain::rustc()); - rustc.current_dir(current_dir).args(&["--print", "sysroot"]); - let stdout = utf8_stdout(rustc)?; - let sysroot_path = AbsPath::assert(Path::new(stdout.trim())); - let mut src = get_rust_src(sysroot_path); - if src.is_none() { - let mut rustup = Command::new(toolchain::rustup()); - rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]); - utf8_stdout(rustup)?; - src = get_rust_src(sysroot_path); - } - match src { - Some(r) => Ok(r), - None => bail!( - "can't load standard library from sysroot\n\ - {}\n\ - (discovered via `rustc --print sysroot`)\n\ - try running `rustup component add rust-src` or set `RUST_SRC_PATH`", - sysroot_path.display(), - ), - } -} - -fn get_rust_src(sysroot_path: &AbsPath) -> Option { - // try the new path first since the old one still exists - let mut src_path = sysroot_path.join("lib/rustlib/src/rust/library"); - if !src_path.exists() { - // FIXME: remove this path when 1.47 comes out - // https://github.com/rust-lang/rust/pull/73265 - src_path = sysroot_path.join("lib/rustlib/src/rust/src"); - } - if src_path.exists() { - Some(src_path) - } else { - None - } -} - -impl SysrootCrateData { - pub fn root_dir(&self) -> &AbsPath { - self.root.parent().unwrap() - } -} - -const SYSROOT_CRATES: &str = " -alloc -core -panic_abort -panic_unwind -proc_macro -profiler_builtins -rtstartup -std -stdarch -term -test -unwind"; - -const STD_DEPS: &str = " -alloc -core -panic_abort -panic_unwind -profiler_builtins -rtstartup -proc_macro -stdarch -term -test -unwind"; diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 440b1cd13..c6102bf27 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -37,7 +37,7 @@ lsp-server = "0.3.3" flycheck = { path = "../flycheck" } ra_ide = { path = "../ra_ide" } profile = { path = "../profile" } -ra_project_model = { path = "../ra_project_model" } +project_model = { path = "../project_model" } syntax = { path = "../syntax" } text_edit = { path = "../text_edit" } vfs = { path = "../vfs" } diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index f177f8709..bade31ca2 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -6,7 +6,7 @@ mod args; use std::{convert::TryFrom, process}; use lsp_server::Connection; -use ra_project_model::ProjectManifest; +use project_model::ProjectManifest; use rust_analyzer::{ cli, config::{Config, LinkedProject}, diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 9c7a9cce6..5ba30dbad 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs @@ -1,8 +1,8 @@ //! See `CargoTargetSpec` use cfg::CfgExpr; +use project_model::{self, TargetKind}; use ra_ide::{FileId, RunnableKind, TestId}; -use ra_project_model::{self, TargetKind}; use vfs::AbsPathBuf; use crate::{global_state::GlobalStateSnapshot, Result}; diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index a43bf2244..f6cb144c6 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -4,9 +4,9 @@ use std::{path::Path, sync::Arc}; use anyhow::Result; use crossbeam_channel::{unbounded, Receiver}; +use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; use ra_db::CrateGraph; use ra_ide::{AnalysisChange, AnalysisHost}; -use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; use vfs::{loader::Handle, AbsPath, AbsPathBuf}; use crate::reload::{ProjectFolders, SourceRootConfig}; diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 70b4512d0..bfc84147c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -11,8 +11,8 @@ use std::{ffi::OsString, path::PathBuf}; use flycheck::FlycheckConfig; use lsp_types::ClientCapabilities; +use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; -use ra_project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; use serde::Deserialize; use vfs::AbsPathBuf; diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 8c115c8a6..2e8b708d0 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -9,9 +9,9 @@ use crossbeam_channel::{unbounded, Receiver, Sender}; use flycheck::FlycheckHandle; use lsp_types::{SemanticTokens, Url}; use parking_lot::{Mutex, RwLock}; +use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; use ra_db::{CrateId, VfsPath}; use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; -use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; use rustc_hash::FxHashMap; use crate::{ diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 86e7833f2..4b5ca7eec 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -18,11 +18,11 @@ use lsp_types::{ SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, }; +use project_model::TargetKind; use ra_ide::{ FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, }; -use ra_project_model::TargetKind; use serde::{Deserialize, Serialize}; use serde_json::to_value; use stdx::{format_to, split_once}; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 32962b088..9a779cb14 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -21,7 +21,7 @@ use crate::{ lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, Result, }; -use ra_project_model::ProjectWorkspace; +use project_model::ProjectWorkspace; use vfs::ChangeKind; pub fn main_loop(config: Config, connection: Connection) -> Result<()> { diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 611c9a89f..640417dc6 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -2,9 +2,9 @@ use std::{mem, sync::Arc}; use flycheck::FlycheckHandle; +use project_model::{ProcMacroClient, ProjectWorkspace}; use ra_db::{CrateGraph, SourceRoot, VfsPath}; use ra_ide::AnalysisChange; -use ra_project_model::{ProcMacroClient, ProjectWorkspace}; use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind}; use crate::{ @@ -98,14 +98,14 @@ impl GlobalState { .iter() .map(|project| match project { LinkedProject::ProjectManifest(manifest) => { - ra_project_model::ProjectWorkspace::load( + project_model::ProjectWorkspace::load( manifest.clone(), &cargo_config, with_sysroot, ) } LinkedProject::InlineJsonProject(it) => { - Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) + Ok(project_model::ProjectWorkspace::Json { project: it.clone() }) } }) .collect::>(); diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 15866fbb1..5bafeba79 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs @@ -12,7 +12,7 @@ use lsp_types::{ notification::Exit, request::Shutdown, TextDocumentIdentifier, Url, WorkDoneProgress, }; use lsp_types::{ProgressParams, ProgressParamsValue}; -use ra_project_model::ProjectManifest; +use project_model::ProjectManifest; use rust_analyzer::{ config::{ClientCapsConfig, Config, FilesConfig, FilesWatcher, LinkedProject}, main_loop, diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 0188aaa2e..2e68e71db 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -198,7 +198,7 @@ impl TidyDocs { "mbe", "parser", "profile", - "ra_project_model", + "project_model", "syntax", "tt", "ra_hir_ty", -- cgit v1.2.3