use super::{messages::MessageDecoder, observation::schema};
use ic_host_process::{
child::OwnedChild,
tool::{
CompletedExecution, OutputLimits, OutputStream, SuccessfulExit, ToolError,
communicate_child_with_observer,
},
};
use std::{
collections::BTreeSet,
io,
path::{Path, PathBuf},
};
#[cfg(test)]
mod tests;
#[must_use = "check execution status and protocol evidence before admitting artifacts"]
pub struct CargoExecution {
pub execution: Result<CompletedExecution, ToolError>,
pub messages: io::Result<()>,
}
pub fn communicate_cargo(
child: &mut OwnedChild,
limits: OutputLimits,
message_bytes: usize,
cancelled: impl FnMut() -> bool,
mut output: impl FnMut(OutputStream, &[u8]) -> io::Result<()>,
mut message: impl FnMut(schema::Message) -> io::Result<()>,
) -> CargoExecution {
let mut decoder = MessageDecoder::new(message_bytes, true);
let mut protocol_error = None;
let execution = communicate_child_with_observer(
child,
None,
limits,
SuccessfulExit::Cleanup,
cancelled,
|stream, bytes| {
if stream == OutputStream::Stdout && protocol_error.is_none() {
protocol_error = decoder.feed(bytes, &mut message).err();
}
output(stream, bytes)
},
);
let messages = if execution
.as_ref()
.is_ok_and(|evidence| evidence.status.success())
{
protocol_error.map_or_else(|| decoder.finish(&mut message), Err)
} else {
protocol_error.map_or(Ok(()), Err)
};
CargoExecution {
execution,
messages,
}
}
#[derive(Debug, thiserror::Error)]
pub enum CargoArtifactError {
#[error("Cargo artifact manifest cannot be resolved: {0}")]
Io(#[from] io::Error),
#[error(
"Cargo did not report exactly one cdylib Wasm for {manifest}; observed {count} outputs"
)]
Selection {
manifest: PathBuf,
count: usize,
},
}
pub fn select_cdylib_wasm(
messages: &[schema::Message],
manifest: &Path,
) -> Result<PathBuf, CargoArtifactError> {
let manifest = manifest.canonicalize()?;
let mut outputs = BTreeSet::new();
for message in messages {
let schema::Message::CompilerArtifact(artifact) = message else {
continue;
};
if artifact
.target
.crate_types
.contains(&schema::CrateType::CDyLib)
&& artifact.manifest_path.canonicalize()? == manifest
{
outputs.extend(wasm_artifact_paths(artifact).map(Path::to_path_buf));
}
}
let count = outputs.len();
match outputs.pop_first() {
Some(path) if count == 1 => Ok(path),
_ => Err(CargoArtifactError::Selection { manifest, count }),
}
}
pub(crate) fn wasm_artifact_paths(artifact: &schema::Artifact) -> impl Iterator<Item = &Path> {
artifact
.filenames
.iter()
.filter(|path| path.extension() == Some("wasm"))
.map(|path| path.as_std_path())
}