Skip to main content

cargo2meson/
lib.rs

1use std::{borrow::Borrow, collections::BTreeSet, io};
2
3use cargo_metadata::{camino::Utf8Path, CrateType, Target, TargetKind};
4use io_extra::IoErrorExt as _;
5use petgraph::{
6    algo::{has_path_connecting, DfsSpace},
7    prelude::DiGraphMap,
8    visit::NodeFiltered,
9};
10use serde::Deserialize;
11
12/// Remove all build scripts and their ancestors,
13/// emitting a meson build script to the given writer.
14pub fn mesonify<W: io::Write>(
15    unit_graph: UnitGraph,
16    strip_prefix: Option<&Utf8Path>,
17    writer: W,
18) -> io::Result<()> {
19    let mut w = writer;
20    let UnitGraph { units, roots } = unit_graph;
21    let mut graph = DiGraphMap::from_edges(
22        units
23            .iter()
24            .enumerate()
25            .flat_map(|(ix, unit)| unit.dependencies.iter().map(move |dep| (dep.index, ix, ()))),
26    );
27    for ix in graph.nodes() {
28        if ix > units.len() {
29            return Err(io::Error::invalid_data("graph indices out of bounds"));
30        }
31    }
32
33    let (is_build_script, had_build_script) = graph
34        .all_edges()
35        .filter_map(|(from, to, ())| Some((from, units[from].is_build_script().then_some(to)?)))
36        .unzip::<_, _, BTreeSet<_>, BTreeSet<_>>();
37
38    for ix in is_build_script {
39        graph.remove_node(ix);
40    }
41
42    if roots.is_empty() {
43        return Err(io::Error::invalid_data("graph must have nonempty roots"));
44    };
45
46    let graph = NodeFiltered::from_fn(&graph, |node| {
47        let mut space = DfsSpace::new(&graph);
48        roots
49            .iter()
50            .any(|root| has_path_connecting(&graph, node, *root, Some(&mut space)))
51    });
52
53    writeln!(w, "rust = import('rust')")?;
54    writeln!(w)?;
55    writeln!(w, "# caller may pass more compiler args")?;
56    writeln!(w, "if not is_variable('unit2args')")?;
57    writeln!(w, "    unit2args = {{}}")?;
58    writeln!(w, "endif")?;
59    writeln!(w)?;
60    writeln!(w, "# caller may override the name given to the unit")?;
61    writeln!(w, "if not is_variable('unit2name')")?;
62    writeln!(w, "    unit2name = {{}}")?;
63    writeln!(w, "endif")?;
64    writeln!(w)?;
65
66    for ix in petgraph::algo::toposort(&graph, None)
67        .map_err(|_| io::Error::invalid_data("cycle in dependency graph"))?
68    {
69        let unit @ Unit {
70            pkg_id,
71            target:
72                Target {
73                    name,
74                    src_path,
75                    edition,
76                    ..
77                },
78            features,
79            dependencies,
80            ..
81        } = &units[ix];
82        let src_path = match strip_prefix {
83            Some(it) => src_path
84                .strip_prefix(it)
85                .map_err(io::Error::invalid_input)?,
86            None => src_path,
87        };
88        writeln!(w)?;
89        writeln!(w, "# {pkg_id}")?;
90        if had_build_script.contains(&ix) {
91            writeln!(w, "# (had build script)")?
92        }
93        let f = if unit.is_library() {
94            "library"
95        } else if unit.is_proc_macro() {
96            "rust.proc_macro"
97        } else {
98            return Err(io::Error::unsupported(format!("{unit:?}")));
99        };
100        writeln!(w, "unit{ix} = {f}(")?;
101        writeln!(w, "    unit2name.get('{ix}', 'unit{ix}'), # {name}")?;
102        writeln!(w, "    sources: structured_sources('{src_path}'),")?;
103        writeln!(w, "    rust_args: [")?;
104        writeln!(w, "        '--edition={edition}',")?;
105        for feature in features {
106            writeln!(w, "        '--cfg', 'feature=\"{feature}\"',")?;
107        }
108        writeln!(w, "        unit2args.get('{ix}', []),")?;
109        writeln!(w, "    ],")?; // rust_args
110        let non_build_script_deps = dependencies.iter().filter_map(|it| {
111            let unit = &units[it.index];
112            match unit.is_build_script() {
113                true => None,
114                false => Some((unit, it)),
115            }
116        });
117        if non_build_script_deps.clone().count() != 0 {
118            writeln!(w, "    link_with: [")?;
119            for (unit, dep) in non_build_script_deps.clone() {
120                writeln!(w, "        unit{}, # {}", dep.index, unit.target.name)?
121            }
122            writeln!(w, "    ],")?; // link_with
123
124            writeln!(w, "    rust_dependency_map: {{")?;
125            for (
126                _,
127                Dependency {
128                    index,
129                    extern_crate_name,
130                },
131            ) in non_build_script_deps
132            {
133                if !unit.is_build_script() {
134                    writeln!(w, "        unit{index}.name(): '{extern_crate_name}',")?
135                }
136            }
137            writeln!(w, "    }},")?; // rust_dependency_map
138        }
139        writeln!(w, ")")?; // unit
140    }
141    Ok(())
142}
143
144/// Emit the build graph in graphviz format.
145pub fn dotify<U: Borrow<Unit>, W: io::Write>(
146    units: impl IntoIterator<Item = U>,
147    writer: W,
148) -> io::Result<()> {
149    let mut w = writer;
150    writeln!(w, "digraph {{")?;
151    for (ix, unit) in units.into_iter().enumerate() {
152        let Unit {
153            pkg_id,
154            target:
155                Target {
156                    name,
157                    kind,
158                    crate_types,
159                    ..
160                },
161            mode,
162            dependencies,
163            ..
164        } = unit.borrow();
165        let pre = match pkg_id.split_once(" (") {
166            Some((pre, _)) => pre,
167            None => pkg_id,
168        };
169        let label = format!("\"{name} @ {pre}\"",);
170        let color = match (mode, &**kind, &**crate_types) {
171            (Mode::Build, [TargetKind::CustomBuild], [CrateType::Bin]) => "blue",
172            (Mode::RunCustomBuild, [TargetKind::CustomBuild], [CrateType::Bin]) => "yellow",
173            (Mode::Build, [TargetKind::Lib], [CrateType::Lib]) => "green",
174            (Mode::Build, [TargetKind::ProcMacro], [CrateType::ProcMacro]) => "purple",
175            _ => "red",
176        };
177        writeln!(w, "    unit{ix} [label={label} color={color}];")?;
178        for Dependency { index, .. } in dependencies {
179            writeln!(w, "        unit{index} -> unit{ix};")?
180        }
181    }
182
183    writeln!(w, "}}")?; // digraph
184    Ok(())
185}
186
187/// Simplified root of the result of `cargo +nightly build -Zunstable-options --unit-graph`
188#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
189pub struct UnitGraph {
190    pub units: Vec<Unit>,
191    pub roots: Vec<usize>,
192}
193
194/// See [`UnitGraph`].
195#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
196pub struct Unit {
197    pub pkg_id: String,
198    pub target: Target,
199    pub mode: Mode,
200    pub features: Vec<String>,
201    pub dependencies: Vec<Dependency>,
202}
203
204impl Unit {
205    pub fn is_build_script(&self) -> bool {
206        matches!(
207            (&*self.target.kind, &*self.target.crate_types),
208            ([TargetKind::CustomBuild], [CrateType::Bin])
209        )
210    }
211    pub fn is_library(&self) -> bool {
212        matches!(
213            (&*self.target.kind, &*self.target.crate_types, &self.mode),
214            ([TargetKind::Lib], [CrateType::Lib], Mode::Build)
215        )
216    }
217    pub fn is_proc_macro(&self) -> bool {
218        matches!(
219            (&*self.target.kind, &*self.target.crate_types, &self.mode),
220            ([TargetKind::ProcMacro], [CrateType::ProcMacro], Mode::Build)
221        )
222    }
223}
224
225/// See [`Unit`].
226#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
227#[serde(rename_all = "kebab-case")]
228pub enum Mode {
229    Build,
230    RunCustomBuild,
231}
232
233/// See [`Unit`].
234#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
235pub struct Dependency {
236    pub index: usize,
237    pub extern_crate_name: String,
238}