Skip to main content

chaud_hot/workspace/graph/
krate.rs

1use 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    // In case the same dependency shows up multiple times, e.g. as a "build"
84    // and "normal" dependency.
85    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}