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
12pub 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, " ],")?; 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, " ],")?; 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, " }},")?; }
139 writeln!(w, ")")?; }
141 Ok(())
142}
143
144pub 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, "}}")?; Ok(())
185}
186
187#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
189pub struct UnitGraph {
190 pub units: Vec<Unit>,
191 pub roots: Vec<usize>,
192}
193
194#[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#[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#[derive(Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
235pub struct Dependency {
236 pub index: usize,
237 pub extern_crate_name: String,
238}