ic_build/cargo/execution/
mod.rs1use super::{messages::MessageDecoder, observation::schema};
7use ic_host_process::{
8 child::OwnedChild,
9 tool::{
10 CompletedExecution, OutputLimits, OutputStream, SuccessfulExit, ToolError,
11 communicate_child_with_observer,
12 },
13};
14use std::{
15 collections::BTreeSet,
16 io,
17 path::{Path, PathBuf},
18};
19
20mod invocation;
21#[cfg(test)]
22mod tests;
23#[cfg(test)]
24pub(crate) use invocation::command_identity;
25pub use invocation::{
26 CargoExecution, CargoExecutionPolicy, CargoInvocation, CargoRecipe, execute_cargo,
27};
28pub(crate) use invocation::{CargoTool, TOOL_IDENTITY_BYTES};
29
30#[must_use = "check execution status and protocol evidence before admitting artifacts"]
36pub(crate) struct CargoCommunication {
37 pub execution: Result<CompletedExecution, ToolError>,
39 pub messages: io::Result<()>,
41}
42
43pub(crate) fn communicate_cargo(
53 child: &mut OwnedChild,
54 limits: OutputLimits,
55 message_bytes: usize,
56 cancelled: impl FnMut() -> bool,
57 mut output: impl FnMut(OutputStream, &[u8]) -> io::Result<()>,
58 mut message: impl FnMut(schema::Message) -> io::Result<()>,
59) -> CargoCommunication {
60 let mut decoder = MessageDecoder::new(message_bytes, true);
61 let mut protocol_error = None;
62 let execution = communicate_child_with_observer(
63 child,
64 None,
65 limits,
66 SuccessfulExit::Cleanup,
67 cancelled,
68 |stream, bytes| {
69 if stream == OutputStream::Stdout && protocol_error.is_none() {
70 protocol_error = decoder.feed(bytes, &mut message).err();
71 }
72 output(stream, bytes)
73 },
74 );
75 let messages = if execution
76 .as_ref()
77 .is_ok_and(|evidence| evidence.status.success())
78 {
79 protocol_error.map_or_else(|| decoder.finish(&mut message), Err)
80 } else {
81 protocol_error.map_or(Ok(()), Err)
82 };
83 CargoCommunication {
84 execution,
85 messages,
86 }
87}
88
89#[derive(Debug, thiserror::Error)]
91pub enum CargoArtifactError {
92 #[error("Cargo artifact manifest cannot be resolved: {0}")]
94 Io(#[from] io::Error),
95 #[error(
97 "Cargo did not report exactly one cdylib Wasm for {manifest}; observed {count} outputs"
98 )]
99 Selection {
100 manifest: PathBuf,
102 count: usize,
104 },
105}
106
107pub fn select_cdylib_wasm(
115 messages: &[schema::Message],
116 manifest: &Path,
117) -> Result<PathBuf, CargoArtifactError> {
118 let manifest = manifest.canonicalize()?;
119 let mut outputs = BTreeSet::new();
120 for message in messages {
121 let schema::Message::CompilerArtifact(artifact) = message else {
122 continue;
123 };
124 if artifact
125 .target
126 .crate_types
127 .contains(&schema::CrateType::CDyLib)
128 && artifact.manifest_path.canonicalize()? == manifest
129 {
130 outputs.extend(wasm_artifact_paths(artifact).map(Path::to_path_buf));
131 }
132 }
133 let count = outputs.len();
134 match outputs.pop_first() {
135 Some(path) if count == 1 => Ok(path),
136 _ => Err(CargoArtifactError::Selection { manifest, count }),
137 }
138}
139
140pub(crate) fn wasm_artifact_paths(artifact: &schema::Artifact) -> impl Iterator<Item = &Path> {
141 artifact
142 .filenames
143 .iter()
144 .filter(|path| path.extension() == Some("wasm"))
145 .map(|path| path.as_std_path())
146}