use std::path::PathBuf;
use std::process::Command;
use std::time::Instant;
use anyhow::{bail, Context, Result};
use crate::build::{BuildContext, BuildResult, PlatformBuilder};
pub struct BenchesBuilder {}
impl BenchesBuilder {
pub fn new() -> Self {
Self {}
}
fn build_dir(&self, ctx: &BuildContext) -> PathBuf {
let release_subdir = if ctx.options.release { "release" } else { "debug" };
ctx.project_root.join("cmake_build").join(release_subdir).join("benches")
}
fn install_dir(&self, ctx: &BuildContext) -> PathBuf {
self.build_dir(ctx).join("out")
}
fn cmake_generator(&self) -> &str {
if cfg!(target_os = "windows") {
"Visual Studio 16 2019"
} else {
"Unix Makefiles"
}
}
fn cmake_extra_flags(&self, ctx: &BuildContext) -> Result<Vec<String>> {
let mut flags = vec![
"-DBENCHMARK_SUPPORT=ON".to_string(),
];
if let Some(cmake_dir) = ctx.ccgo_cmake_dir() {
flags.push(format!("-DCCGO_CMAKE_DIR={}", cmake_dir.display()));
}
if !cfg!(target_os = "windows") {
let build_type = if ctx.options.release {
"Release"
} else {
"Debug"
};
flags.push(format!("-DCMAKE_BUILD_TYPE={}", build_type));
}
if cfg!(target_os = "macos") {
flags.push("-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9".to_string());
flags.push("-DENABLE_BITCODE=0".to_string());
}
if let Some(cache) = ctx.compiler_cache() {
for (name, value) in cache.cmake_variables() {
flags.push(format!("-D{}={}", name, value));
}
}
Ok(flags)
}
fn build_benchmarks(&self, ctx: &BuildContext, incremental: bool) -> Result<()> {
let build_dir = self.build_dir(ctx);
let install_dir = self.install_dir(ctx);
if !incremental && build_dir.exists() {
std::fs::remove_dir_all(&build_dir)
.with_context(|| format!("Failed to clean {}", build_dir.display()))?;
}
std::fs::create_dir_all(&build_dir)
.with_context(|| format!("Failed to create {}", build_dir.display()))?;
if ctx.options.verbose {
eprintln!("Building benchmarks in {}...", build_dir.display());
}
let mut cmake_cmd = Command::new("cmake");
cmake_cmd
.arg("-S")
.arg(&ctx.project_root)
.arg("-B")
.arg(&build_dir)
.arg("-G")
.arg(self.cmake_generator());
let extra_flags = self.cmake_extra_flags(ctx)?;
for flag in &extra_flags {
cmake_cmd.arg(flag);
}
if ctx.options.verbose {
eprintln!("CMake configure: {:?}", cmake_cmd);
}
let status = cmake_cmd.status().context("Failed to run CMake configure")?;
if !status.success() {
bail!("CMake configure failed");
}
let mut build_cmd = Command::new("cmake");
build_cmd
.arg("--build")
.arg(".")
.arg("--target")
.arg("install")
.current_dir(&build_dir);
if cfg!(target_os = "windows") {
let build_type = if ctx.options.release {
"Release"
} else {
"Debug"
};
build_cmd.arg("--config").arg(build_type);
}
if let Some(jobs) = ctx.options.jobs {
build_cmd.arg("--parallel").arg(jobs.to_string());
} else {
build_cmd.arg("--parallel").arg("8");
}
if ctx.options.verbose {
eprintln!("CMake build: {:?}", build_cmd);
}
let status = build_cmd.status().context("Failed to run CMake build")?;
if !status.success() {
bail!("CMake build failed");
}
if ctx.options.verbose {
eprintln!("Benchmarks built successfully: {}", install_dir.display());
}
Ok(())
}
fn find_benchmark_executables(&self, ctx: &BuildContext) -> Result<Vec<PathBuf>> {
let install_dir = self.install_dir(ctx);
let mut executables = Vec::new();
if !install_dir.exists() {
bail!("Benchmark install directory not found: {}", install_dir.display());
}
for entry in std::fs::read_dir(&install_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let file_name = path.file_name().unwrap().to_string_lossy();
if file_name.contains("_googlebenchmark") || file_name.contains("_bench") {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = std::fs::metadata(&path)?;
if metadata.permissions().mode() & 0o111 != 0 {
executables.push(path);
}
}
#[cfg(windows)]
{
if file_name.ends_with(".exe") {
executables.push(path);
}
}
}
}
}
if executables.is_empty() {
bail!("No benchmark executables found in {}", install_dir.display());
}
Ok(executables)
}
pub fn run_benchmarks(&self, ctx: &BuildContext, filter: Option<&str>) -> Result<()> {
let executables = self.find_benchmark_executables(ctx)?;
let build_dir = self.build_dir(ctx);
let now = chrono::Local::now();
let timestamp = now.format("%Y%m%d_%H%M%S_%6f");
let system_name = if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "windows") {
"windows"
} else {
"linux"
};
let json_output = build_dir.join(format!(
"benches_on_{}_result_{}.json",
system_name, timestamp
));
for exe in executables {
eprintln!("\nRunning benchmark: {}", exe.display());
let mut cmd = Command::new(&exe);
if let Some(filter_str) = filter {
cmd.arg(format!("--benchmark_filter={}", filter_str));
}
cmd.arg("--benchmark_format=console");
cmd.arg(format!("--benchmark_out={}", json_output.display()));
if ctx.options.verbose {
eprintln!("Executing: {:?}", cmd);
}
let status = cmd.status().with_context(|| {
format!("Failed to run benchmark executable {}", exe.display())
})?;
if !status.success() {
bail!("Benchmark {} failed", exe.display());
}
eprintln!("✓ Benchmark completed: {}", exe.display());
}
eprintln!("\nBenchmark results: {}", json_output.display());
Ok(())
}
pub fn generate_ide_project(&self, ctx: &BuildContext) -> Result<()> {
let build_dir = self.build_dir(ctx);
if build_dir.exists() {
std::fs::remove_dir_all(&build_dir)
.with_context(|| format!("Failed to clean {}", build_dir.display()))?;
}
std::fs::create_dir_all(&build_dir)
.with_context(|| format!("Failed to create {}", build_dir.display()))?;
eprintln!("Generating IDE project in {}...", build_dir.display());
let mut cmake_cmd = Command::new("cmake");
cmake_cmd
.arg("-S")
.arg(&ctx.project_root)
.arg("-B")
.arg(&build_dir)
.arg("-G")
.arg(self.cmake_generator());
let extra_flags = self.cmake_extra_flags(ctx)?;
for flag in &extra_flags {
cmake_cmd.arg(flag);
}
if ctx.options.verbose {
eprintln!("CMake configure: {:?}", cmake_cmd);
}
let status = cmake_cmd.status().context("Failed to run CMake configure")?;
if !status.success() {
bail!("CMake configure failed");
}
let project_file = if cfg!(target_os = "macos") {
build_dir.join(format!("{}.xcodeproj", ctx.lib_name()))
} else if cfg!(target_os = "windows") {
build_dir.join(format!("{}.sln", ctx.lib_name()))
} else {
build_dir.join(format!("{}.workspace", ctx.lib_name()))
};
if project_file.exists() {
eprintln!("\n✓ IDE project generated: {}", project_file.display());
#[cfg(target_os = "macos")]
{
let _ = Command::new("open").arg(&project_file).status();
}
#[cfg(target_os = "windows")]
{
let _ = Command::new("cmd")
.arg("/C")
.arg("start")
.arg(&project_file)
.status();
}
} else {
eprintln!("\n✓ IDE project files generated in: {}", build_dir.display());
}
Ok(())
}
}
impl PlatformBuilder for BenchesBuilder {
fn platform_name(&self) -> &str {
"benches"
}
fn default_architectures(&self) -> Vec<String> {
vec![]
}
fn validate_prerequisites(&self, _ctx: &BuildContext) -> Result<()> {
if !which::which("cmake").is_ok() {
bail!("CMake is required but not found in PATH");
}
Ok(())
}
fn build(&self, ctx: &BuildContext) -> Result<BuildResult> {
let start = Instant::now();
self.validate_prerequisites(ctx)?;
self.build_benchmarks(ctx, false)?;
let duration = start.elapsed();
Ok(BuildResult {
sdk_archive: self.install_dir(ctx),
symbols_archive: None,
aar_archive: None,
duration_secs: duration.as_secs_f64(),
architectures: vec![],
})
}
fn clean(&self, ctx: &BuildContext) -> Result<()> {
for subdir in &["release", "debug"] {
let build_dir = ctx.project_root.join("cmake_build").join(subdir).join("benches");
if build_dir.exists() {
std::fs::remove_dir_all(&build_dir)
.with_context(|| format!("Failed to clean {}", build_dir.display()))?;
}
}
for old_dir in &[
ctx.project_root.join("cmake_build/Benches"),
ctx.project_root.join("cmake_build/benches"),
] {
if old_dir.exists() {
std::fs::remove_dir_all(old_dir)
.with_context(|| format!("Failed to clean {}", old_dir.display()))?;
}
}
Ok(())
}
}
impl Default for BenchesBuilder {
fn default() -> Self {
Self::new()
}
}