use cargo_metadata::Message;
use log::{debug, trace};
use std::{
io::{self, BufReader},
path::{Path, PathBuf},
process::{Command, Stdio},
};
pub struct CargoBuilder {
command: Command,
}
pub struct CargoBuildResult {
messages: Vec<Message>,
}
impl CargoBuilder {
pub fn new() -> Self {
let mut args = vec!["build", "--lib", "--message-format", "json"];
if !cfg!(debug_assertions) {
args.push("--release");
}
let mut command = Command::new(env!("CARGO"));
command
.args(&args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null());
Self { command }
}
pub fn arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Self {
self.command.arg(arg);
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
self.command.current_dir(dir);
self
}
pub fn build(&mut self) -> io::Result<CargoBuildResult> {
debug!(command:% = {
let program = self.command.get_program();
let args = self.command.get_args();
let mut command = vec![program];
command.extend(args);
command.join(" ".as_ref()).to_string_lossy().to_string()
}; "run cargo build command");
let mut child = self.command.spawn()?;
let stdout = child
.stdout
.take()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to capture stdout"))?;
let reader = BufReader::new(stdout);
let mut messages = Vec::new();
for message in cargo_metadata::Message::parse_stream(reader) {
trace!(message:?; "cargo build message");
let message = message?;
messages.push(message);
}
let exit_status = child.wait()?;
if !exit_status.success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Cargo build failed with exit status: {}", exit_status),
));
}
Ok(CargoBuildResult { messages })
}
}
impl Default for CargoBuilder {
fn default() -> Self {
Self::new()
}
}
impl CargoBuildResult {
pub fn get_cdylib(&self) -> Option<PathBuf> {
self.messages.iter().rev().find_map(|msg| {
if let Message::CompilerArtifact(artifact) = msg {
artifact.filenames.iter().find_map(|filename| {
let ext = filename.extension();
if matches!(ext, Some("so") | Some("dylib") | Some("dll")) {
Some(PathBuf::from(filename.as_std_path()))
} else {
None
}
})
} else {
None
}
})
}
}