use anyhow::{Context, Result};
use cargo_metadata::MetadataCommand;
use serde::Deserialize;
use std::io::BufRead;
use std::path::PathBuf;
use std::process::{Command, Stdio};
#[macro_export]
macro_rules! status {
($verbose:expr, $($arg:tt)*) => {
if $verbose {
eprintln!($($arg)*);
}
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
eprintln!($($arg)*);
};
}
#[derive(Debug)]
pub struct ProjectInfo {
#[allow(dead_code)]
pub name: String,
pub manifest_dir: PathBuf,
}
pub fn load_project_info() -> Result<ProjectInfo> {
let metadata = MetadataCommand::new()
.exec()
.context("Failed to execute cargo metadata")?;
let root_package = metadata
.root_package()
.context("No root package found. Make sure you're in a Cargo project directory.")?;
Ok(ProjectInfo {
name: (*root_package.name).clone(),
manifest_dir: root_package.manifest_path.parent().unwrap().into(),
})
}
#[derive(Debug, Deserialize)]
struct CargoMessage {
reason: String,
#[serde(default)]
executable: Option<String>,
#[serde(default)]
target: Option<CargoTarget>,
}
#[derive(Debug, Deserialize)]
struct CargoTarget {
#[allow(dead_code)]
name: String,
kind: Vec<String>,
}
pub struct BuildResult {
pub exe_path: PathBuf,
}
pub fn build_and_get_exe(
manifest_dir: &PathBuf,
cargo_args: &[String],
verbose: bool,
) -> Result<BuildResult> {
let mut cmd = Command::new("cargo");
cmd.arg("build")
.arg("--message-format=json")
.current_dir(manifest_dir)
.stdout(Stdio::piped());
cmd.stderr(Stdio::inherit());
if verbose {
cmd.arg("--verbose");
}
for arg in cargo_args {
cmd.arg(arg);
}
let mut child = cmd.spawn().context("Failed to spawn cargo build")?;
let stdout = child.stdout.take().context("Failed to capture stdout")?;
let reader = std::io::BufReader::new(stdout);
let mut exe_path: Option<PathBuf> = None;
for line in reader.lines() {
let line: String = line.context("Failed to read cargo output")?;
if let Ok(msg) = serde_json::from_str::<CargoMessage>(&line) {
if msg.reason == "compiler-artifact" {
if let Some(exe) = msg.executable {
if let Some(target) = &msg.target {
if target.kind.contains(&"bin".to_string()) {
exe_path = Some(PathBuf::from(exe));
}
}
}
}
}
}
let status = child.wait().context("Failed to wait for cargo build")?;
if !status.success() {
anyhow::bail!("cargo build failed");
}
let exe_path =
exe_path.context("No executable found in build output. Is this a binary crate?")?;
Ok(BuildResult { exe_path })
}