1 //! `rust-project.json` file format.
3 //! This format is spiritually a serialization of [`base_db::CrateGraph`]. The
4 //! idea here is that people who do not use Cargo, can instead teach their build
5 //! system to generate `rust-project.json` which can be ingested by
8 use std::path::PathBuf;
10 use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
11 use paths::{AbsPath, AbsPathBuf};
12 use rustc_hash::FxHashMap;
13 use serde::{de, Deserialize};
15 use crate::cfg_flag::CfgFlag;
17 /// Roots and crates that compose this Rust project.
18 #[derive(Clone, Debug, Eq, PartialEq)]
19 pub struct ProjectJson {
20 /// e.g. `path/to/sysroot`
21 pub(crate) sysroot: Option<AbsPathBuf>,
22 /// e.g. `path/to/sysroot/lib/rustlib/src/rust`
23 pub(crate) sysroot_src: Option<AbsPathBuf>,
24 project_root: AbsPathBuf,
28 /// A crate points to the root module of a crate and lists the dependencies of the crate. This is
29 /// useful in creating the crate graph.
30 #[derive(Clone, Debug, Eq, PartialEq)]
32 pub(crate) display_name: Option<CrateDisplayName>,
33 pub(crate) root_module: AbsPathBuf,
34 pub(crate) edition: Edition,
35 pub(crate) version: Option<String>,
36 pub(crate) deps: Vec<Dependency>,
37 pub(crate) cfg: Vec<CfgFlag>,
38 pub(crate) target: Option<String>,
39 pub(crate) env: FxHashMap<String, String>,
40 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
41 pub(crate) is_workspace_member: bool,
42 pub(crate) include: Vec<AbsPathBuf>,
43 pub(crate) exclude: Vec<AbsPathBuf>,
44 pub(crate) is_proc_macro: bool,
45 pub(crate) repository: Option<String>,
49 /// Create a new ProjectJson instance.
53 /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`)
54 /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via
56 pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
58 sysroot: data.sysroot.map(|it| base.join(it)),
59 sysroot_src: data.sysroot_src.map(|it| base.join(it)),
60 project_root: base.to_path_buf(),
65 let is_workspace_member = crate_data.is_workspace_member.unwrap_or_else(|| {
66 crate_data.root_module.is_relative()
67 && !crate_data.root_module.starts_with("..")
68 || crate_data.root_module.starts_with(base)
70 let root_module = base.join(crate_data.root_module).normalize();
71 let (include, exclude) = match crate_data.source {
73 let absolutize = |dirs: Vec<PathBuf>| {
75 .map(|it| base.join(it).normalize())
78 (absolutize(src.include_dirs), absolutize(src.exclude_dirs))
80 None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
84 display_name: crate_data
86 .map(CrateDisplayName::from_canonical_name),
88 edition: crate_data.edition.into(),
89 version: crate_data.version.as_ref().map(ToString::to_string),
94 Dependency::new(dep_data.name, CrateId(dep_data.krate as u32))
98 target: crate_data.target,
100 proc_macro_dylib_path: crate_data
101 .proc_macro_dylib_path
102 .map(|it| base.join(it)),
106 is_proc_macro: crate_data.is_proc_macro,
107 repository: crate_data.repository,
110 .collect::<Vec<_>>(),
113 /// Returns the number of crates in the project.
114 pub fn n_crates(&self) -> usize {
117 /// Returns an iterator over the crates in the project.
118 pub fn crates(&self) -> impl Iterator<Item = (CrateId, &Crate)> + '_ {
119 self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate))
121 /// Returns the path to the project's root folder.
122 pub fn path(&self) -> &AbsPath {
127 #[derive(Deserialize, Debug, Clone)]
128 pub struct ProjectJsonData {
129 sysroot: Option<PathBuf>,
130 sysroot_src: Option<PathBuf>,
131 crates: Vec<CrateData>,
134 #[derive(Deserialize, Debug, Clone)]
136 display_name: Option<String>,
137 root_module: PathBuf,
138 edition: EditionData,
140 version: Option<semver::Version>,
144 target: Option<String>,
146 env: FxHashMap<String, String>,
147 proc_macro_dylib_path: Option<PathBuf>,
148 is_workspace_member: Option<bool>,
149 source: Option<CrateSource>,
153 repository: Option<String>,
156 #[derive(Deserialize, Debug, Clone)]
157 #[serde(rename = "edition")]
159 #[serde(rename = "2015")]
161 #[serde(rename = "2018")]
163 #[serde(rename = "2021")]
167 impl From<EditionData> for Edition {
168 fn from(data: EditionData) -> Self {
170 EditionData::Edition2015 => Edition::Edition2015,
171 EditionData::Edition2018 => Edition::Edition2018,
172 EditionData::Edition2021 => Edition::Edition2021,
177 #[derive(Deserialize, Debug, Clone)]
179 /// Identifies a crate by position in the crates array.
180 #[serde(rename = "crate")]
182 #[serde(deserialize_with = "deserialize_crate_name")]
186 #[derive(Deserialize, Debug, Clone)]
188 include_dirs: Vec<PathBuf>,
189 exclude_dirs: Vec<PathBuf>,
192 fn deserialize_crate_name<'de, D>(de: D) -> Result<CrateName, D::Error>
194 D: de::Deserializer<'de>,
196 let name = String::deserialize(de)?;
197 CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err)))