Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/project-model/src/project_json.rs')
-rw-r--r--crates/project-model/src/project_json.rs193
1 files changed, 193 insertions, 0 deletions
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
new file mode 100644
index 0000000000..a3c5ac1674
--- /dev/null
+++ b/crates/project-model/src/project_json.rs
@@ -0,0 +1,193 @@
+//! `rust-project.json` file format.
+//!
+//! This format is spiritually a serialization of [`base_db::CrateGraph`]. The
+//! idea here is that people who do not use Cargo, can instead teach their build
+//! system to generate `rust-project.json` which can be ingested by
+//! rust-analyzer.
+
+use std::path::PathBuf;
+
+use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
+use paths::{AbsPath, AbsPathBuf};
+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) sysroot_src: Option<AbsPathBuf>,
+ project_root: AbsPathBuf,
+ crates: Vec<Crate>,
+}
+
+/// 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) display_name: Option<CrateDisplayName>,
+ pub(crate) root_module: AbsPathBuf,
+ pub(crate) edition: Edition,
+ pub(crate) version: Option<String>,
+ pub(crate) deps: Vec<Dependency>,
+ pub(crate) cfg: Vec<CfgFlag>,
+ pub(crate) target: Option<String>,
+ pub(crate) env: FxHashMap<String, String>,
+ pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
+ pub(crate) is_workspace_member: bool,
+ pub(crate) include: Vec<AbsPathBuf>,
+ pub(crate) exclude: Vec<AbsPathBuf>,
+ pub(crate) is_proc_macro: bool,
+ pub(crate) repository: Option<String>,
+}
+
+impl ProjectJson {
+ /// Create a new ProjectJson instance.
+ ///
+ /// # Arguments
+ ///
+ /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`)
+ /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via
+ /// configuration.
+ pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
+ ProjectJson {
+ sysroot_src: data.sysroot_src.map(|it| base.join(it)),
+ project_root: base.to_path_buf(),
+ 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).normalize();
+ let (include, exclude) = match crate_data.source {
+ Some(src) => {
+ let absolutize = |dirs: Vec<PathBuf>| {
+ dirs.into_iter()
+ .map(|it| base.join(it).normalize())
+ .collect::<Vec<_>>()
+ };
+ (absolutize(src.include_dirs), absolutize(src.exclude_dirs))
+ }
+ None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
+ };
+
+ Crate {
+ display_name: crate_data
+ .display_name
+ .map(CrateDisplayName::from_canonical_name),
+ root_module,
+ edition: crate_data.edition.into(),
+ version: crate_data.version.as_ref().map(ToString::to_string),
+ deps: crate_data
+ .deps
+ .into_iter()
+ .map(|dep_data| {
+ Dependency::new(dep_data.name, CrateId(dep_data.krate as u32))
+ })
+ .collect::<Vec<_>>(),
+ 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,
+ is_proc_macro: crate_data.is_proc_macro,
+ repository: crate_data.repository,
+ }
+ })
+ .collect::<Vec<_>>(),
+ }
+ }
+ /// Returns the number of crates in the project.
+ pub fn n_crates(&self) -> usize {
+ self.crates.len()
+ }
+ /// Returns an iterator over the crates in the project.
+ pub fn crates(&self) -> impl Iterator<Item = (CrateId, &Crate)> + '_ {
+ self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate))
+ }
+ /// Returns the path to the project's root folder.
+ pub fn path(&self) -> &AbsPath {
+ &self.project_root
+ }
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct ProjectJsonData {
+ sysroot_src: Option<PathBuf>,
+ crates: Vec<CrateData>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+struct CrateData {
+ display_name: Option<String>,
+ root_module: PathBuf,
+ edition: EditionData,
+ #[serde(default)]
+ version: Option<semver::Version>,
+ deps: Vec<DepData>,
+ #[serde(default)]
+ cfg: Vec<CfgFlag>,
+ target: Option<String>,
+ #[serde(default)]
+ env: FxHashMap<String, String>,
+ proc_macro_dylib_path: Option<PathBuf>,
+ is_workspace_member: Option<bool>,
+ source: Option<CrateSource>,
+ #[serde(default)]
+ is_proc_macro: bool,
+ #[serde(default)]
+ repository: Option<String>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename = "edition")]
+enum EditionData {
+ #[serde(rename = "2015")]
+ Edition2015,
+ #[serde(rename = "2018")]
+ Edition2018,
+ #[serde(rename = "2021")]
+ Edition2021,
+}
+
+impl From<EditionData> for Edition {
+ fn from(data: EditionData) -> Self {
+ match data {
+ EditionData::Edition2015 => Edition::Edition2015,
+ EditionData::Edition2018 => Edition::Edition2018,
+ EditionData::Edition2021 => Edition::Edition2021,
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, Clone)]
+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, Debug, Clone)]
+struct CrateSource {
+ include_dirs: Vec<PathBuf>,
+ exclude_dirs: Vec<PathBuf>,
+}
+
+fn deserialize_crate_name<'de, D>(de: D) -> Result<CrateName, D::Error>
+where
+ D: de::Deserializer<'de>,
+{
+ let name = String::deserialize(de)?;
+ CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err)))
+}