1use base_db::{CrateDisplayName, CrateName};
53use cfg::CfgAtom;
54use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
55use rustc_hash::{FxHashMap, FxHashSet};
56use serde::{Deserialize, Serialize, de};
57use span::Edition;
58
59use crate::{ManifestPath, TargetKind};
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct ProjectJson {
64 pub(crate) sysroot: Option<AbsPathBuf>,
66 pub(crate) sysroot_src: Option<AbsPathBuf>,
68 pub(crate) sysroot_project: Option<Box<ProjectJson>>,
70 project_root: AbsPathBuf,
71 manifest: Option<ManifestPath>,
74 crates: Vec<Crate>,
75 runnables: Vec<Runnable>,
79}
80
81impl std::ops::Index<CrateArrayIdx> for ProjectJson {
82 type Output = Crate;
83 fn index(&self, index: CrateArrayIdx) -> &Self::Output {
84 &self.crates[index.0]
85 }
86}
87
88impl ProjectJson {
89 pub fn new(
97 manifest: Option<ManifestPath>,
98 base: &AbsPath,
99 data: ProjectJsonData,
100 ) -> ProjectJson {
101 let absolutize_on_base = |p| base.absolutize(p);
102 let sysroot_src = data.sysroot_src.map(absolutize_on_base);
103 let sysroot_project =
104 data.sysroot_project.zip(sysroot_src.clone()).map(|(sysroot_data, sysroot_src)| {
105 Box::new(ProjectJson::new(None, &sysroot_src, *sysroot_data))
106 });
107
108 ProjectJson {
109 sysroot: data.sysroot.map(absolutize_on_base),
110 sysroot_src,
111 sysroot_project,
112 project_root: base.to_path_buf(),
113 manifest,
114 runnables: data.runnables.into_iter().map(Runnable::from).collect(),
115 crates: data
116 .crates
117 .into_iter()
118 .map(|crate_data| {
119 let root_module = absolutize_on_base(crate_data.root_module);
120 let is_workspace_member = crate_data
121 .is_workspace_member
122 .unwrap_or_else(|| root_module.starts_with(base));
123 let (include, exclude) = match crate_data.source {
124 Some(src) => {
125 let absolutize = |dirs: Vec<Utf8PathBuf>| {
126 dirs.into_iter().map(absolutize_on_base).collect::<Vec<_>>()
127 };
128 (absolutize(src.include_dirs), absolutize(src.exclude_dirs))
129 }
130 None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
131 };
132
133 let build = match crate_data.build {
134 Some(build) => Some(Build {
135 label: build.label,
136 build_file: build.build_file,
137 target_kind: build.target_kind.into(),
138 }),
139 None => None,
140 };
141
142 let cfg = crate_data
143 .cfg_groups
144 .iter()
145 .flat_map(|cfg_extend| {
146 let cfg_group = data.cfg_groups.get(cfg_extend);
147 match cfg_group {
148 Some(cfg_group) => cfg_group.0.iter().cloned(),
149 None => {
150 tracing::error!(
151 "Unknown cfg group `{cfg_extend}` in crate `{}`",
152 crate_data.display_name.as_deref().unwrap_or("<unknown>"),
153 );
154 [].iter().cloned()
155 }
156 }
157 })
158 .chain(crate_data.cfg.0)
159 .collect();
160
161 Crate {
162 display_name: crate_data
163 .display_name
164 .as_deref()
165 .map(CrateDisplayName::from_canonical_name),
166 root_module,
167 edition: crate_data.edition.into(),
168 version: crate_data.version.as_ref().map(ToString::to_string),
169 deps: crate_data.deps,
170 cfg,
171 target: crate_data.target,
172 env: crate_data.env,
173 crate_attrs: crate_data.crate_attrs,
174 proc_macro_dylib_path: crate_data
175 .proc_macro_dylib_path
176 .map(absolutize_on_base),
177 is_workspace_member,
178 include,
179 exclude,
180 is_proc_macro: crate_data.is_proc_macro,
181 repository: crate_data.repository,
182 build,
183 proc_macro_cwd: crate_data.proc_macro_cwd.map(absolutize_on_base),
184 }
185 })
186 .collect(),
187 }
188 }
189
190 pub fn n_crates(&self) -> usize {
192 self.crates.len()
193 }
194
195 pub fn crates(&self) -> impl Iterator<Item = (CrateArrayIdx, &Crate)> {
197 self.crates.iter().enumerate().map(|(idx, krate)| (CrateArrayIdx(idx), krate))
198 }
199
200 pub fn path(&self) -> &AbsPath {
202 &self.project_root
203 }
204
205 pub fn crate_by_root(&self, root: &AbsPath) -> Option<&Crate> {
206 self.crates
207 .iter()
208 .filter(|krate| krate.is_workspace_member)
209 .find(|krate| krate.root_module == root)
210 }
211
212 pub fn manifest(&self) -> Option<&ManifestPath> {
214 self.manifest.as_ref()
215 }
216
217 pub fn crate_by_buildfile(&self, path: &AbsPath) -> Option<Build> {
218 let path: &std::path::Path = path.as_ref();
220 self.crates
221 .iter()
222 .filter(|krate| krate.is_workspace_member)
223 .filter_map(|krate| krate.build.as_ref())
224 .find(|build| build.build_file.as_std_path() == path)
225 .cloned()
226 }
227
228 pub fn crate_by_label(&self, label: &str) -> Option<&Crate> {
229 self.crates
231 .iter()
232 .filter(|krate| krate.is_workspace_member)
233 .find(|krate| krate.build.as_ref().is_some_and(|build| build.label == label))
234 }
235
236 pub fn manifest_or_root(&self) -> &AbsPath {
238 self.manifest.as_ref().map_or(&self.project_root, |manifest| manifest.as_ref())
239 }
240
241 pub fn project_root(&self) -> &AbsPath {
243 &self.project_root
244 }
245
246 pub fn runnables(&self) -> &[Runnable] {
247 &self.runnables
248 }
249
250 pub fn runnable_template(&self, kind: RunnableKind) -> Option<&Runnable> {
251 self.runnables().iter().find(|r| r.kind == kind)
252 }
253}
254
255#[derive(Clone, Debug, Eq, PartialEq)]
258pub struct Crate {
259 pub(crate) display_name: Option<CrateDisplayName>,
260 pub root_module: AbsPathBuf,
261 pub(crate) edition: Edition,
262 pub(crate) version: Option<String>,
263 pub(crate) deps: Vec<Dep>,
264 pub(crate) cfg: Vec<CfgAtom>,
265 pub(crate) target: Option<String>,
266 pub(crate) env: FxHashMap<String, String>,
267 pub(crate) crate_attrs: Vec<String>,
269 pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
270 pub(crate) is_workspace_member: bool,
271 pub(crate) include: Vec<AbsPathBuf>,
272 pub(crate) exclude: Vec<AbsPathBuf>,
273 pub(crate) is_proc_macro: bool,
274 pub(crate) proc_macro_cwd: Option<AbsPathBuf>,
276 pub(crate) repository: Option<String>,
277 pub build: Option<Build>,
278}
279
280impl Crate {
281 pub fn iter_deps(&self) -> impl ExactSizeIterator<Item = CrateArrayIdx> {
282 self.deps.iter().map(|dep| dep.krate)
283 }
284}
285
286#[derive(Clone, Debug, Eq, PartialEq)]
288pub struct Build {
289 pub label: String,
298 pub build_file: Utf8PathBuf,
304 pub target_kind: TargetKind,
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct Runnable {
338 pub program: String,
342 pub args: Vec<String>,
348 pub cwd: Utf8PathBuf,
350 pub kind: RunnableKind,
351}
352
353#[derive(Debug, Clone, PartialEq, Eq)]
355pub enum RunnableKind {
356 Check,
358
359 Run,
362
363 TestOne,
367
368 TestMod,
372
373 DocTestOne,
377
378 BenchOne,
382
383 Flycheck,
386
387 Unknown,
389}
390
391#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
392pub struct ProjectJsonData {
393 sysroot: Option<Utf8PathBuf>,
394 sysroot_src: Option<Utf8PathBuf>,
395 sysroot_project: Option<Box<ProjectJsonData>>,
396 #[serde(default)]
397 cfg_groups: FxHashMap<String, CfgList>,
398 crates: Vec<CrateData>,
399 #[serde(default)]
400 runnables: Vec<RunnableData>,
401 }
404
405#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)]
406#[serde(transparent)]
407struct CfgList(#[serde(with = "cfg_")] Vec<CfgAtom>);
408
409#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
410struct CrateData {
411 display_name: Option<String>,
412 root_module: Utf8PathBuf,
413 edition: EditionData,
414 version: Option<semver::Version>,
415 deps: Vec<Dep>,
416 #[serde(default)]
417 cfg_groups: FxHashSet<String>,
418 #[serde(default)]
419 cfg: CfgList,
420 target: Option<String>,
421 #[serde(default)]
422 env: FxHashMap<String, String>,
423 #[serde(default)]
424 crate_attrs: Vec<String>,
425 proc_macro_dylib_path: Option<Utf8PathBuf>,
426 is_workspace_member: Option<bool>,
427 source: Option<CrateSource>,
428 #[serde(default)]
429 is_proc_macro: bool,
430 repository: Option<String>,
431 build: Option<BuildData>,
432 proc_macro_cwd: Option<Utf8PathBuf>,
433}
434
435mod cfg_ {
436 use cfg::CfgAtom;
437 use serde::{Deserialize, Serialize};
438
439 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Vec<CfgAtom>, D::Error>
440 where
441 D: serde::Deserializer<'de>,
442 {
443 let cfg: Vec<String> = Vec::deserialize(deserializer)?;
444 cfg.into_iter().map(|it| crate::parse_cfg(&it).map_err(serde::de::Error::custom)).collect()
445 }
446 pub(super) fn serialize<S>(cfg: &[CfgAtom], serializer: S) -> Result<S::Ok, S::Error>
447 where
448 S: serde::Serializer,
449 {
450 cfg.iter()
451 .map(|cfg| match cfg {
452 CfgAtom::Flag(flag) => flag.as_str().to_owned(),
453 CfgAtom::KeyValue { key, value } => {
454 format!("{}=\"{}\"", key.as_str(), value.as_str())
455 }
456 })
457 .collect::<Vec<String>>()
458 .serialize(serializer)
459 }
460}
461
462#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
463#[serde(rename = "edition")]
464enum EditionData {
465 #[serde(rename = "2015")]
466 Edition2015,
467 #[serde(rename = "2018")]
468 Edition2018,
469 #[serde(rename = "2021")]
470 Edition2021,
471 #[serde(rename = "2024")]
472 Edition2024,
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
476struct BuildData {
477 label: String,
478 build_file: Utf8PathBuf,
479 target_kind: TargetKindData,
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
483struct RunnableData {
484 program: String,
485 args: Vec<String>,
486 cwd: Utf8PathBuf,
487 kind: RunnableKindData,
488}
489
490#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
491#[serde(rename_all = "camelCase")]
492enum RunnableKindData {
493 Flycheck,
494 Check,
495 Run,
496 TestOne,
497 TestMod,
498 DocTestOne,
499 BenchOne,
500
501 #[allow(unused)]
503 #[serde(other)]
504 Unknown,
505}
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
508#[serde(rename_all = "camelCase")]
509enum TargetKindData {
510 Bin,
511 Lib,
513 Test,
514}
515#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq, Hash)]
520#[serde(transparent)]
521pub struct CrateArrayIdx(pub usize);
522
523#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
524pub(crate) struct Dep {
525 #[serde(rename = "crate")]
527 pub(crate) krate: CrateArrayIdx,
528 #[serde(serialize_with = "serialize_crate_name")]
529 #[serde(deserialize_with = "deserialize_crate_name")]
530 pub(crate) name: CrateName,
531}
532
533#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
534struct CrateSource {
535 include_dirs: Vec<Utf8PathBuf>,
536 exclude_dirs: Vec<Utf8PathBuf>,
537}
538
539impl From<TargetKindData> for TargetKind {
540 fn from(data: TargetKindData) -> Self {
541 match data {
542 TargetKindData::Bin => TargetKind::Bin,
543 TargetKindData::Lib => TargetKind::Lib { is_proc_macro: false },
544 TargetKindData::Test => TargetKind::Test,
545 }
546 }
547}
548
549impl From<EditionData> for Edition {
550 fn from(data: EditionData) -> Self {
551 match data {
552 EditionData::Edition2015 => Edition::Edition2015,
553 EditionData::Edition2018 => Edition::Edition2018,
554 EditionData::Edition2021 => Edition::Edition2021,
555 EditionData::Edition2024 => Edition::Edition2024,
556 }
557 }
558}
559
560impl From<RunnableData> for Runnable {
561 fn from(data: RunnableData) -> Self {
562 Runnable { program: data.program, args: data.args, cwd: data.cwd, kind: data.kind.into() }
563 }
564}
565
566impl From<RunnableKindData> for RunnableKind {
567 fn from(data: RunnableKindData) -> Self {
568 match data {
569 RunnableKindData::Check => RunnableKind::Check,
570 RunnableKindData::Run => RunnableKind::Run,
571 RunnableKindData::TestOne => RunnableKind::TestOne,
572 RunnableKindData::TestMod => RunnableKind::TestMod,
573 RunnableKindData::DocTestOne => RunnableKind::DocTestOne,
574 RunnableKindData::BenchOne => RunnableKind::BenchOne,
575 RunnableKindData::Flycheck => RunnableKind::Flycheck,
576 RunnableKindData::Unknown => RunnableKind::Unknown,
577 }
578 }
579}
580
581fn deserialize_crate_name<'de, D>(de: D) -> std::result::Result<CrateName, D::Error>
582where
583 D: de::Deserializer<'de>,
584{
585 let name = String::deserialize(de)?;
586 CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {err:?}")))
587}
588
589fn serialize_crate_name<S>(name: &CrateName, se: S) -> Result<S::Ok, S::Error>
590where
591 S: serde::Serializer,
592{
593 se.serialize_str(name.as_str())
594}