flowey_lib_common/
run_cargo_doc.rs1use crate::_util::cargo_output;
10use flowey::node::prelude::*;
11use flowey::shell::FloweyCmd;
12use std::collections::BTreeMap;
13#[derive(Serialize, Deserialize)]
14pub struct CargoDocCommands {
15 cmds: Vec<Vec<String>>,
16 cargo_work_dir: PathBuf,
17 no_incremental: bool,
18}
19
20impl CargoDocCommands {
21 pub fn run(self, rt: &RustRuntimeServices<'_>) -> anyhow::Result<PathBuf> {
24 self.run_with(rt, |x| x)
25 }
26
27 pub fn run_with(
33 self,
34 rt: &RustRuntimeServices<'_>,
35 f: impl Fn(FloweyCmd<'_>) -> FloweyCmd<'_>,
36 ) -> anyhow::Result<PathBuf> {
37 let Self {
38 cmds,
39 cargo_work_dir,
40 no_incremental,
41 } = self;
42
43 let out_dir = rt.sh.current_dir();
44 rt.sh.change_dir(cargo_work_dir);
45
46 let mut json = String::new();
47 for mut cmd in cmds {
48 let argv0 = cmd.remove(0);
49 let cmd = flowey::shell_cmd!(rt, "{argv0} {cmd...}");
50 let cmd = if no_incremental {
51 cmd.env("CARGO_INCREMENTAL", "0")
52 } else {
53 cmd
54 };
55 let cmd = f(cmd);
56 json.push_str(&cmd.read()?);
57 }
58 let messages: Vec<cargo_output::Message> = serde_json::Deserializer::from_str(&json)
59 .into_iter()
60 .collect::<Result<_, _>>()?;
61
62 let cargo_out_dir = messages
64 .iter()
65 .find_map(|msg| match msg {
66 cargo_output::Message::CompilerArtifact { filenames, .. } => {
67 filenames.iter().find_map(|filename| {
68 filename
69 .file_name()
70 .is_some_and(|f| f == "index.html")
71 .then(|| filename.parent().and_then(Path::parent))
72 .flatten()
73 })
74 }
75 _ => None,
76 })
77 .context("could not find cargo doc output directory")?;
78
79 anyhow::ensure!(
80 cargo_out_dir.file_name().is_some_and(|name| name == "doc"),
81 "unexpected cargo doc output directory {}",
82 cargo_out_dir.display()
83 );
84
85 let final_dir = out_dir.join("cargo-doc-out");
86 fs_err::rename(cargo_out_dir, &final_dir)?;
87 Ok(final_dir)
88 }
89}
90
91#[derive(Serialize, Deserialize)]
93pub enum DocPackageKind {
94 Workspace { exclude: Vec<String> },
96 Crate(String),
98 NoStdCrate(String),
103}
104
105#[derive(Serialize, Deserialize)]
107pub struct DocPackage {
108 pub kind: DocPackageKind,
110 pub no_deps: bool,
112 pub document_private_items: bool,
114}
115
116flowey_request! {
117 pub struct Request {
118 pub in_folder: ReadVar<PathBuf>,
119 pub packages: Vec<DocPackage>,
121 pub target_triple: target_lexicon::Triple,
123 pub cargo_cmd: WriteVar<CargoDocCommands>,
124 }
125}
126
127#[derive(Default)]
128struct ResolvedDocPackages {
129 workspace: Option<(bool, bool)>,
131 exclude: Vec<String>,
132 crates: BTreeMap<(bool, bool), Vec<String>>,
133 crates_no_std: BTreeMap<(bool, bool), Vec<String>>,
134}
135
136new_flow_node!(struct Node);
137
138impl FlowNode for Node {
139 type Request = Request;
140
141 fn imports(ctx: &mut ImportCtx<'_>) {
142 ctx.import::<crate::cfg_cargo_common_flags::Node>();
143 ctx.import::<crate::install_rust::Node>();
144 }
145
146 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
147 let rust_toolchain = ctx.reqv(crate::install_rust::Request::GetRustupToolchain);
148 let flags = ctx.reqv(crate::cfg_cargo_common_flags::Request::GetFlags);
149
150 for Request {
151 in_folder,
152 packages,
153 target_triple,
154 cargo_cmd,
155 } in requests
156 {
157 ctx.req(crate::install_rust::Request::InstallTargetTriple(
158 target_triple.clone(),
159 ));
160
161 let mut targets = ResolvedDocPackages::default();
163 for DocPackage {
164 kind,
165 no_deps,
166 document_private_items,
167 } in packages
168 {
169 match kind {
170 DocPackageKind::Workspace { exclude } => {
171 if targets.workspace.is_some() {
172 anyhow::bail!("cannot pass Workspace variant multiple times")
173 }
174 targets.exclude.extend(exclude);
175 targets.workspace = Some((no_deps, document_private_items))
176 }
177 DocPackageKind::Crate(name) => targets
178 .crates
179 .entry((no_deps, document_private_items))
180 .or_default()
181 .push(name),
182 DocPackageKind::NoStdCrate(name) => targets
183 .crates_no_std
184 .entry((no_deps, document_private_items))
185 .or_default()
186 .push(name),
187 }
188 }
189
190 let doc_targets = targets;
191
192 ctx.emit_minor_rust_step("construct cargo doc command", |ctx| {
193 let rust_toolchain = rust_toolchain.clone().claim(ctx);
194 let flags = flags.clone().claim(ctx);
195 let in_folder = in_folder.claim(ctx);
196 let write_doc_cmd = cargo_cmd.claim(ctx);
197
198 move |rt| {
199 let rust_toolchain = rt.read(rust_toolchain);
200 let flags = rt.read(flags);
201 let in_folder = rt.read(in_folder);
202
203 let crate::cfg_cargo_common_flags::Flags {
204 locked,
205 verbose,
206 no_incremental,
207 } = flags;
208
209 let mut cmds = Vec::new();
210 let ResolvedDocPackages {
211 workspace,
212 exclude,
213 mut crates,
214 crates_no_std,
215 } = doc_targets;
216
217 let base_cmd = |no_deps: bool, document_private_items: bool| -> Vec<String> {
218 let mut v = Vec::new();
219 v.push("cargo".into());
220 if let Some(rust_toolchain) = &rust_toolchain {
221 v.push(format!("+{rust_toolchain}"))
222 }
223 v.push("doc".into());
224 v.push("--message-format=json-render-diagnostics".into());
225 v.push("--target".into());
226 v.push(target_triple.to_string());
227 if locked {
228 v.push("--locked".into());
229 }
230 if verbose {
231 v.push("--verbose".into());
232 }
233 if no_deps {
234 v.push("--no-deps".into());
235 }
236 if document_private_items {
237 v.push("--document-private-items".into())
238 }
239 v
240 };
241
242 if let Some((no_deps, document_private_items)) = workspace {
245 crates.remove(&(no_deps, document_private_items));
247
248 let mut v = base_cmd(no_deps, document_private_items);
249
250 v.push("--workspace".into());
251
252 for crates_no_std in crates_no_std.values() {
253 for c in crates_no_std.iter().chain(exclude.iter()) {
254 v.push("--exclude".into());
255 v.push(c.into())
256 }
257 }
258
259 cmds.push(v);
260 }
261
262 for ((no_deps, document_private_items), crates) in crates {
264 let mut v = base_cmd(no_deps, document_private_items);
265
266 for c in crates {
267 v.push("-p".into());
268 v.push(c);
269 }
270
271 cmds.push(v)
272 }
273
274 for ((no_deps, document_private_items), crates) in crates_no_std {
276 let mut v = base_cmd(no_deps, document_private_items);
277
278 for c in crates {
279 v.push("-p".into());
280 v.push(c);
281 }
282
283 cmds.push(v)
284 }
285
286 let cmd = CargoDocCommands {
287 cmds,
288 cargo_work_dir: in_folder.clone(),
289 no_incremental,
290 };
291
292 rt.write(write_doc_cmd, &cmd);
293 }
294 });
295 }
296
297 Ok(())
298 }
299}