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