use crate::bench_target::BenchTarget;
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
#[derive(Debug)]
pub enum CompileError {
CompileFailed(ExitStatus),
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompileError::CompileFailed(exit_status) => write!(
f,
"'cargo bench' returned an error ({}); unable to continue.",
exit_status
),
}
}
}
impl std::error::Error for CompileError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CompileError::CompileFailed(_) => None,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
struct Target {
name: String,
kind: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "reason")]
#[allow(clippy::enum_variant_names)]
enum Message {
#[serde(rename = "compiler-artifact")]
CompilerArtifact {
target: Target,
executable: Option<PathBuf>,
},
#[serde(rename = "compiler-message")]
CompilerMessage {},
#[serde(rename = "build-script-executed")]
BuildScriptExecuted { linked_paths: Vec<String> },
#[serde(rename = "build-finished")]
BuildFinished {},
}
#[derive(Debug)]
pub struct CompiledBenchmarks {
pub targets: Vec<BenchTarget>,
pub library_paths: Vec<PathBuf>,
}
pub fn compile(debug_build: bool, cargo_args: &[std::ffi::OsString]) -> Result<CompiledBenchmarks> {
let subcommand: &[&'static str] = if debug_build {
&["test", "--benches"]
} else {
&["bench"]
};
let mut cargo = Command::new("cargo")
.args(subcommand)
.args(cargo_args)
.args(&["--no-run", "--message-format", "json-render-diagnostics"])
.stdin(Stdio::null())
.stderr(Stdio::inherit()) .stdout(Stdio::piped()) .spawn()?;
let cargo_stdout = cargo
.stdout
.take()
.expect("Child process doesn't have a stdout handle");
let stream = serde_json::Deserializer::from_reader(cargo_stdout).into_iter::<Message>();
let mut targets = vec![];
let mut library_paths = vec![];
for message in stream {
let message = message.context("Failed to parse message from cargo")?;
match message {
Message::CompilerArtifact { target, executable } => {
if target
.kind
.iter()
.any(|kind| kind == "bench" || kind == "test" || kind == "lib")
{
if let Some(executable) = executable {
targets.push(BenchTarget {
name: target.name,
executable,
});
}
}
}
Message::BuildScriptExecuted { linked_paths } => {
for path in linked_paths {
let path = path
.replace("dependency=", "")
.replace("crate=", "")
.replace("native=", "")
.replace("framework=", "")
.replace("all=", "");
let path = PathBuf::from(path);
library_paths.push(path);
}
}
_ => (),
}
}
targets.sort_by(|target1, target2| (target1.name).cmp(&target2.name));
let exit_status = cargo
.wait()
.context("Cargo compilation failed in an unexpected way")?;
if !(exit_status.success()) {
Err(CompileError::CompileFailed(exit_status).into())
} else {
Ok(CompiledBenchmarks {
targets,
library_paths,
})
}
}