]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/project-model/src/project_json.rs
Rollup merge of #102054 - GuillaumeGomez:sidebar-all-page, r=notriddle
[rust.git] / src / tools / rust-analyzer / crates / project-model / src / project_json.rs
1 //! `rust-project.json` file format.
2 //!
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
6 //! rust-analyzer.
7
8 use std::path::PathBuf;
9
10 use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
11 use paths::{AbsPath, AbsPathBuf};
12 use rustc_hash::FxHashMap;
13 use serde::{de, Deserialize};
14
15 use crate::cfg_flag::CfgFlag;
16
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,
25     crates: Vec<Crate>,
26 }
27
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)]
31 pub struct Crate {
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>,
46 }
47
48 impl ProjectJson {
49     /// Create a new ProjectJson instance.
50     ///
51     /// # Arguments
52     ///
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
55     ///            configuration.
56     pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
57         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(),
61             crates: data
62                 .crates
63                 .into_iter()
64                 .map(|crate_data| {
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)
69                     });
70                     let root_module = base.join(crate_data.root_module).normalize();
71                     let (include, exclude) = match crate_data.source {
72                         Some(src) => {
73                             let absolutize = |dirs: Vec<PathBuf>| {
74                                 dirs.into_iter()
75                                     .map(|it| base.join(it).normalize())
76                                     .collect::<Vec<_>>()
77                             };
78                             (absolutize(src.include_dirs), absolutize(src.exclude_dirs))
79                         }
80                         None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
81                     };
82
83                     Crate {
84                         display_name: crate_data
85                             .display_name
86                             .map(CrateDisplayName::from_canonical_name),
87                         root_module,
88                         edition: crate_data.edition.into(),
89                         version: crate_data.version.as_ref().map(ToString::to_string),
90                         deps: crate_data
91                             .deps
92                             .into_iter()
93                             .map(|dep_data| {
94                                 Dependency::new(dep_data.name, CrateId(dep_data.krate as u32))
95                             })
96                             .collect::<Vec<_>>(),
97                         cfg: crate_data.cfg,
98                         target: crate_data.target,
99                         env: crate_data.env,
100                         proc_macro_dylib_path: crate_data
101                             .proc_macro_dylib_path
102                             .map(|it| base.join(it)),
103                         is_workspace_member,
104                         include,
105                         exclude,
106                         is_proc_macro: crate_data.is_proc_macro,
107                         repository: crate_data.repository,
108                     }
109                 })
110                 .collect::<Vec<_>>(),
111         }
112     }
113     /// Returns the number of crates in the project.
114     pub fn n_crates(&self) -> usize {
115         self.crates.len()
116     }
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))
120     }
121     /// Returns the path to the project's root folder.
122     pub fn path(&self) -> &AbsPath {
123         &self.project_root
124     }
125 }
126
127 #[derive(Deserialize, Debug, Clone)]
128 pub struct ProjectJsonData {
129     sysroot: Option<PathBuf>,
130     sysroot_src: Option<PathBuf>,
131     crates: Vec<CrateData>,
132 }
133
134 #[derive(Deserialize, Debug, Clone)]
135 struct CrateData {
136     display_name: Option<String>,
137     root_module: PathBuf,
138     edition: EditionData,
139     #[serde(default)]
140     version: Option<semver::Version>,
141     deps: Vec<DepData>,
142     #[serde(default)]
143     cfg: Vec<CfgFlag>,
144     target: Option<String>,
145     #[serde(default)]
146     env: FxHashMap<String, String>,
147     proc_macro_dylib_path: Option<PathBuf>,
148     is_workspace_member: Option<bool>,
149     source: Option<CrateSource>,
150     #[serde(default)]
151     is_proc_macro: bool,
152     #[serde(default)]
153     repository: Option<String>,
154 }
155
156 #[derive(Deserialize, Debug, Clone)]
157 #[serde(rename = "edition")]
158 enum EditionData {
159     #[serde(rename = "2015")]
160     Edition2015,
161     #[serde(rename = "2018")]
162     Edition2018,
163     #[serde(rename = "2021")]
164     Edition2021,
165 }
166
167 impl From<EditionData> for Edition {
168     fn from(data: EditionData) -> Self {
169         match data {
170             EditionData::Edition2015 => Edition::Edition2015,
171             EditionData::Edition2018 => Edition::Edition2018,
172             EditionData::Edition2021 => Edition::Edition2021,
173         }
174     }
175 }
176
177 #[derive(Deserialize, Debug, Clone)]
178 struct DepData {
179     /// Identifies a crate by position in the crates array.
180     #[serde(rename = "crate")]
181     krate: usize,
182     #[serde(deserialize_with = "deserialize_crate_name")]
183     name: CrateName,
184 }
185
186 #[derive(Deserialize, Debug, Clone)]
187 struct CrateSource {
188     include_dirs: Vec<PathBuf>,
189     exclude_dirs: Vec<PathBuf>,
190 }
191
192 fn deserialize_crate_name<'de, D>(de: D) -> Result<CrateName, D::Error>
193 where
194     D: de::Deserializer<'de>,
195 {
196     let name = String::deserialize(de)?;
197     CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err)))
198 }