1use std::path::Path;
2use std::time::Instant;
3
4use anyhow::Context;
5use codegen_unit::CodegenUnit;
6use guppy::graph::{PackageGraph, PackageMetadata};
7use targets::determine_targets;
8
9use crate::codegen_unit::{extract_codegen_units, BinaryInvocation};
10
11mod codegen_plan;
12mod codegen_unit;
13mod config;
14mod shell;
15mod targets;
16
17pub use shell::{Shell, Verbosity};
18
19#[tracing::instrument(level = tracing::Level::DEBUG, name = "Generate crates", skip(cargo_path))]
22pub fn codegen(
23 cargo_path: &str,
24 working_directory: &Path,
25 args: &[String],
26 shell: &mut Shell,
27) -> Result<(), Vec<anyhow::Error>> {
28 let package_graph = package_graph(cargo_path, shell).map_err(|e| vec![e])?;
29 let codegen_plan = compute_filtered_codegen_plan(working_directory, args, &package_graph)?;
30
31 let workspace_dir = package_graph
32 .workspace()
33 .root()
34 .canonicalize()
35 .context("Failed to get the canonical path to the root directory of this workspace")
36 .map_err(|e| vec![e])?;
37 for unit in codegen_plan {
38 generate_crate(&unit, cargo_path, &workspace_dir, shell).map_err(|e| vec![e])?;
39 }
40
41 Ok(())
42}
43
44#[tracing::instrument(level = tracing::Level::DEBUG, name = "Verify freshness", skip(cargo_path))]
47pub fn verify(
48 cargo_path: &str,
49 working_directory: &Path,
50 args: &[String],
51 shell: &mut Shell,
52) -> Result<(), Vec<anyhow::Error>> {
53 let package_graph = package_graph(cargo_path, shell).map_err(|e| vec![e])?;
54 let codegen_plan = compute_filtered_codegen_plan(working_directory, args, &package_graph)?;
55
56 let workspace_dir = package_graph
57 .workspace()
58 .root()
59 .canonicalize()
60 .context("Failed to get the canonical path to the root directory of this workspace")
61 .map_err(|e| vec![e])?;
62 for unit in codegen_plan {
63 let Some(verifier) = &unit.verifier else {
64 return Err(vec![anyhow::anyhow!(
65 "`{}` doesn't define a verifier, therefore we can't verify if it's fresh",
66 unit.package_metadata.name()
67 )]);
68 };
69 verify_crate(
70 verifier,
71 &unit.package_metadata,
72 cargo_path,
73 &workspace_dir,
74 shell,
75 )
76 .map_err(|e| vec![e])?;
77 }
78
79 Ok(())
80}
81
82fn compute_filtered_codegen_plan<'a>(
83 working_directory: &Path,
84 args: &[String],
85 package_graph: &'a PackageGraph,
86) -> Result<Vec<CodegenUnit<'a>>, Vec<anyhow::Error>> {
87 let mut codegen_units = extract_codegen_units(package_graph)?;
88
89 if tracing::event_enabled!(tracing::Level::DEBUG) {
90 let codegen_unit_names: Vec<_> = codegen_units
91 .iter()
92 .map(|unit| unit.package_metadata.name().to_string())
93 .collect();
94 tracing::debug!(
95 ?codegen_unit_names,
96 "Determined the list of codegen units in the current workspace"
97 );
98 }
99
100 let targets = determine_targets(args, working_directory, package_graph);
101
102 if tracing::event_enabled!(tracing::Level::DEBUG) {
103 let target_names: Vec<_> = targets
104 .iter()
105 .map(|id| {
106 package_graph
107 .metadata(id)
108 .expect("Unknown package id")
109 .name()
110 .to_owned()
111 })
112 .collect();
113 tracing::debug!(
114 ?target_names,
115 "Determined the list of target packages for this invocation"
116 );
117 }
118
119 if !targets.is_empty() {
121 let mut depends_cache = package_graph.new_depends_cache();
122 codegen_units.retain(|unit| {
123 targets.iter().any(|target_id| {
124 unit.package_metadata.id() == target_id
125 || depends_cache
126 .depends_on(target_id, unit.package_metadata.id())
127 .unwrap_or(false)
128 })
129 });
130 }
131
132 if tracing::event_enabled!(tracing::Level::DEBUG) {
133 let codegen_unit_names: Vec<_> = codegen_units
134 .iter()
135 .map(|unit| unit.package_metadata.name().to_string())
136 .collect();
137 tracing::debug!(
138 ?codegen_unit_names,
139 "Retaining only the following codegen units for this invocation, based on the target packages"
140 );
141 }
142
143 codegen_plan::codegen_plan(codegen_units, package_graph)
144}
145
146#[tracing::instrument(name = "Verify crate", skip_all, fields(crate_name = %package_metadata.name()))]
147fn verify_crate(
148 verifier: &BinaryInvocation,
149 package_metadata: &PackageMetadata,
150 cargo_path: &str,
151 workspace_path: &Path,
152 shell: &mut Shell,
153) -> Result<(), anyhow::Error> {
154 let be_quiet = shell.verbosity() == Verbosity::Quiet;
155
156 {
158 let timer = Instant::now();
159 let _ = shell.status(
160 "Compiling",
161 format!(
162 "`{}`, the verifier for `{}`",
163 verifier.binary.name,
164 package_metadata.name()
165 ),
166 );
167 let mut cmd = verifier.build_command(cargo_path, be_quiet);
168 cmd.env("CARGO_PX_WORKSPACE_ROOT_DIR", workspace_path)
169 .stdout(std::process::Stdio::inherit())
170 .stderr(std::process::Stdio::inherit());
171
172 let err_msg = || {
173 format!(
174 "Failed to compile `{}`, the verifier for `{}`",
175 verifier.binary.name,
176 package_metadata.name()
177 )
178 };
179
180 let status = cmd.status().with_context(err_msg)?;
181 if !status.success() {
182 anyhow::bail!(err_msg());
183 }
184 let _ = shell.status(
185 "Compiled",
186 format!(
187 "`{}`, the verifier for `{}`, in {:.3}s",
188 verifier.binary.name,
189 package_metadata.name(),
190 timer.elapsed().as_secs_f32()
191 ),
192 );
193 }
194
195 {
197 let timer = Instant::now();
198 let _ = shell.status("Verifying", format!("`{}`", package_metadata.name()));
199 let mut cmd = verifier.run_command(cargo_path, be_quiet);
200
201 cmd.env(
202 "CARGO_PX_GENERATED_PKG_MANIFEST_PATH",
203 package_metadata.manifest_path(),
204 )
205 .env("CARGO_PX_WORKSPACE_ROOT_DIR", workspace_path)
206 .stdout(std::process::Stdio::inherit())
207 .stderr(std::process::Stdio::inherit());
208
209 let err_msg = || {
210 format!(
211 "Failed to run `{}`, the verifier for `{}`",
212 verifier.binary.name,
213 package_metadata.name()
214 )
215 };
216
217 let status = cmd.status().with_context(err_msg)?;
218 if !status.success() {
219 anyhow::bail!(err_msg());
220 }
221 let _ = shell.status(
222 "Verified",
223 format!(
224 "`{}` in {:.3}s",
225 package_metadata.name(),
226 timer.elapsed().as_secs_f32()
227 ),
228 );
229 }
230 Ok(())
231}
232
233#[tracing::instrument(name = "Generate crate", skip_all, fields(crate_name = %unit.package_metadata.name()))]
234fn generate_crate(
235 unit: &codegen_unit::CodegenUnit,
236 cargo_path: &str,
237 workspace_path: &Path,
238 shell: &mut Shell,
239) -> Result<(), anyhow::Error> {
240 let be_quiet = shell.verbosity() == Verbosity::Quiet;
241
242 {
244 let timer = Instant::now();
245 let _ = shell.status(
246 "Compiling",
247 format!(
248 "`{}`, the code generator for `{}`",
249 unit.generator.binary.name,
250 unit.package_metadata.name()
251 ),
252 );
253 let mut cmd = unit.generator.build_command(cargo_path, be_quiet);
254 cmd.env("CARGO_PX_WORKSPACE_ROOT_DIR", workspace_path)
255 .stdout(std::process::Stdio::inherit())
256 .stderr(std::process::Stdio::inherit());
257
258 let err_msg = || {
259 format!(
260 "Failed to compile `{}`, the code generator for `{}`",
261 unit.generator.binary.name,
262 unit.package_metadata.name()
263 )
264 };
265
266 let status = cmd.status().with_context(err_msg)?;
267 if !status.success() {
268 anyhow::bail!(err_msg());
269 }
270 let _ = shell.status(
271 "Compiled",
272 format!(
273 "`{}`, the code generator for `{}`, in {:.3}s",
274 unit.generator.binary.name,
275 unit.package_metadata.name(),
276 timer.elapsed().as_secs_f32()
277 ),
278 );
279 }
280
281 {
283 let timer = Instant::now();
284 let _ = shell.status("Generating", format!("`{}`", unit.package_metadata.name()));
285 let mut cmd = unit.generator.run_command(cargo_path, be_quiet);
286
287 cmd.env(
288 "CARGO_PX_GENERATED_PKG_MANIFEST_PATH",
289 unit.package_metadata.manifest_path(),
290 )
291 .env("CARGO_PX_WORKSPACE_ROOT_DIR", workspace_path)
292 .stdout(std::process::Stdio::inherit())
293 .stderr(std::process::Stdio::inherit());
294
295 let err_msg = || {
296 format!(
297 "Failed to run `{}`, the code generator for package `{}`",
298 unit.generator.binary.name,
299 unit.package_metadata.name()
300 )
301 };
302
303 let status = cmd.status().with_context(err_msg)?;
304 if !status.success() {
305 anyhow::bail!(err_msg());
306 }
307 let _ = shell.status(
308 "Generated",
309 format!(
310 "`{}` in {:.3}s",
311 unit.package_metadata.name(),
312 timer.elapsed().as_secs_f32()
313 ),
314 );
315 }
316 Ok(())
317}
318
319#[tracing::instrument(name = "Compute package graph", skip_all)]
321fn package_graph(cargo_path: &str, shell: &mut Shell) -> Result<PackageGraph, anyhow::Error> {
322 let timer = Instant::now();
323 let _ = shell.status("Computing", "package graph");
324 let mut metadata_cmd = guppy::MetadataCommand::new();
325 metadata_cmd.cargo_path(cargo_path);
326 let package_graph = metadata_cmd
327 .exec()
328 .context("Failed to execute `cargo metadata`")?
329 .build_graph()
330 .context("Failed to build a package graph starting from the output of `cargo metadata`");
331 let _ = shell.status(
332 "Computed",
333 format!("package graph in {:.3}s", timer.elapsed().as_secs_f32()),
334 );
335 package_graph
336}