1use cargo_metadata::Message;
14use log::{debug, trace};
15use std::{
16 io::{self, BufReader},
17 path::{Path, PathBuf},
18 process::{Command, Stdio},
19};
20
21pub struct CargoBuilder {
23 command: Command,
24}
25
26pub struct CargoBuildResult {
28 messages: Vec<Message>,
29}
30
31impl CargoBuilder {
32 pub fn new() -> Self {
34 let mut args = vec!["build", "--lib", "--message-format", "json"];
35 if !cfg!(debug_assertions) {
36 args.push("--release");
37 }
38 let mut command = Command::new(env!("CARGO"));
39 command
40 .args(&args)
41 .stdin(Stdio::null())
42 .stdout(Stdio::piped())
43 .stderr(Stdio::null());
44 Self { command }
45 }
46
47 pub fn arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Self {
49 self.command.arg(arg);
50 self
51 }
52
53 pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
55 self.command.current_dir(dir);
56 self
57 }
58
59 pub fn build(&mut self) -> io::Result<CargoBuildResult> {
61 debug!(command:% = {
62 let program = self.command.get_program();
63 let args = self.command.get_args();
64 let mut command = vec![program];
65 command.extend(args);
66 command.join(" ".as_ref()).to_string_lossy().to_string()
67 }; "run cargo build command");
68
69 let mut child = self.command.spawn()?;
70 let stdout = child
71 .stdout
72 .take()
73 .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to capture stdout"))?;
74 let reader = BufReader::new(stdout);
75 let mut messages = Vec::new();
76 for message in cargo_metadata::Message::parse_stream(reader) {
77 trace!(message:?; "cargo build message");
78 let message = message?;
79 messages.push(message);
80 }
81 let exit_status = child.wait()?;
82 if !exit_status.success() {
83 return Err(io::Error::new(
84 io::ErrorKind::Other,
85 format!("Cargo build failed with exit status: {}", exit_status),
86 ));
87 }
88 Ok(CargoBuildResult { messages })
89 }
90}
91
92impl Default for CargoBuilder {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl CargoBuildResult {
99 pub fn get_cdylib(&self) -> Option<PathBuf> {
101 self.messages.iter().rev().find_map(|msg| {
102 if let Message::CompilerArtifact(artifact) = msg {
103 artifact.filenames.iter().find_map(|filename| {
104 let ext = filename.extension();
105 if matches!(ext, Some("so") | Some("dylib") | Some("dll")) {
106 Some(PathBuf::from(filename.as_std_path()))
107 } else {
108 None
109 }
110 })
111 } else {
112 None
113 }
114 })
115 }
116}