chaud_hot/workspace/graph/
krate.rs1use super::{BuildEnv, KrateIdx, KrateIndex};
2use crate::cargo::metadata::{
3 Dependency, DependencyKind, Package, PackageName, Target, TargetKind, TargetName,
4};
5use crate::util::etx;
6use anyhow::{Context as _, Result, ensure};
7use camino::{Utf8Path, Utf8PathBuf};
8use core::fmt;
9use hashbrown::HashSet;
10
11#[derive(Debug)]
12pub enum KrateDir {
13 Src(Utf8PathBuf),
14 Root(Utf8PathBuf),
15}
16
17impl KrateDir {
18 pub fn path(&self) -> &Utf8Path {
19 match self {
20 KrateDir::Root(p) | KrateDir::Src(p) => p,
21 }
22 }
23}
24
25pub struct Krate {
26 idx: KrateIdx,
27 pkg: PackageName,
28 deps: Box<[KrateIdx]>,
29 dirs: Box<[KrateDir]>,
30}
31
32impl fmt::Display for Krate {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "{}", self.pkg)
35 }
36}
37
38impl Krate {
39 pub(super) fn new(env: &BuildEnv, index: &KrateIndex, pkg: &Package) -> Result<Self> {
40 new_inner(env, index, pkg)
41 .with_context(etx!("Failed to build crate info for {}", pkg.name()))
42 }
43
44 pub fn idx(&self) -> KrateIdx {
45 self.idx
46 }
47
48 pub(super) fn deps(&self) -> &[KrateIdx] {
49 &self.deps
50 }
51
52 pub fn dirs(&self) -> &[KrateDir] {
53 &self.dirs
54 }
55}
56
57fn new_inner(env: &BuildEnv, index: &KrateIndex, package: &Package) -> Result<Krate> {
58 let pkg = package.name().clone();
59 let idx = index
60 .get_pkg(&pkg)
61 .context("Package not found in the index")?;
62
63 let deps = filter_deps(index, package.dependencies());
64
65 let root_bin = match env.root() == idx {
66 true => Some(env.bin()),
67 false => None,
68 };
69 let dirs = krate_dirs(root_bin, package).context("Failed to determine crate dirs")?;
70
71 Ok(Krate { idx, pkg, deps, dirs })
72}
73
74fn filter_deps(index: &KrateIndex, deps: &[Dependency]) -> Box<[KrateIdx]> {
75 let follow = [DependencyKind::Normal, DependencyKind::Build];
76
77 let mut deps: Vec<_> = deps
78 .iter()
79 .filter(|k| follow.contains(&k.kind()))
80 .filter_map(|d| index.get_pkg(d.name()))
81 .collect();
82 deps.sort_unstable();
83 deps.dedup();
86
87 deps.into_boxed_slice()
88}
89
90fn krate_dirs(root_bin: Option<&TargetName>, pkg: &Package) -> Result<Box<[KrateDir]>> {
91 let targets = filter_targets(root_bin, pkg.targets())?;
92 let Some(targets) = targets else {
93 return Ok(Box::new([]));
94 };
95
96 let mani = pkg.manifest_path().to_owned();
97
98 let root_dir = mani
99 .path()
100 .parent()
101 .context("manifest_path has no parent")?;
102
103 let mut dirs = vec![KrateDir::Root(root_dir.to_owned())];
104 let mut seen = HashSet::new();
105 seen.insert(root_dir);
106
107 for target in targets {
108 let dir = target
109 .src_path()
110 .parent()
111 .context("src_path has no parent")?;
112
113 if seen.insert(dir) {
114 dirs.push(KrateDir::Src(dir.to_owned()));
115 }
116 }
117
118 Ok(dirs.into_boxed_slice())
119}
120
121fn filter_targets<'a>(
122 root_bin: Option<&TargetName>,
123 targets: &'a [Target],
124) -> Result<Option<impl Iterator<Item = &'a Target>>> {
125 let is_rlib = |tk| [TargetKind::Lib, TargetKind::RLib].contains(tk);
126
127 let mut bin = None;
128 let mut lib = None;
129 let mut build = None;
130
131 for target in targets {
132 if let Some(root_bin) = root_bin {
133 if target.kind().contains(&TargetKind::Bin) && target.name() == root_bin {
134 ensure!(bin.is_none(), "Multiple root bin targets");
135 bin = Some(target);
136 }
137 }
138
139 if target.kind().iter().any(is_rlib) {
140 ensure!(lib.is_none(), "Multiple lib targets");
141 lib = Some(target);
142 }
143 if target.kind().contains(&TargetKind::CustomBuild) {
144 ensure!(build.is_none(), "Multiple custom-build targets");
145 build = Some(target);
146 }
147 }
148
149 if bin.is_none() && lib.is_none() {
150 return Ok(None);
151 }
152
153 Ok(Some([bin, lib, build].into_iter().flatten()))
154}