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