use anyhow::{Context, bail}; use std::env; use std::path::{Path, PathBuf}; use std::process::Command; fn check_protoc_good(protoc: &Path) -> anyhow::Result<()> { let output = Command::new(protoc) .arg("--version") .output() .context("Failed to execute protoc")?; if !output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); bail!( "protoc --version failed, likely dotslash is missing; \ try `cargo install dotslash`; stdout: {stdout:?}, stderr: {stderr:?}" ); } Ok(()) } fn is_github_actions() -> bool { env::var_os("GITHUB_ACTIONS").is_some() } /// Locate `protoc`. /// /// Search order: `$PROTOC`, then `bin/protoc` walking parents (dotslash /// wrapper), then `$PATH`. A non-executable `bin/protoc` (e.g. dotslash /// missing under Bazel remote execution) is non-fatal — lookup continues /// on `$PATH`. Returns `Ok(None)` when missing outside GitHub Actions. pub fn find_protoc() -> anyhow::Result> { // `$PROTOC` is the prost-build override; Bazel sets it to a hermetic binary. if let Ok(protoc_env) = env::var("PROTOC") { let protoc = PathBuf::from(&protoc_env); if protoc.try_exists()? { check_protoc_good(&protoc)?; return Ok(Some(protoc)); } } let cwd = env::current_dir()?; let mut dir = cwd.clone(); let mut dir_rel = PathBuf::new(); loop { // Relative path keeps cargo rerun fingerprints stable across machines. let protoc = dir_rel.join("bin/protoc"); if protoc.try_exists()? { match check_protoc_good(&protoc) { Ok(()) => return Ok(Some(protoc)), Err(e) => { // Dotslash wrapper present but not runnable — try PATH next. eprintln!( "bin/protoc found at `{}` but failed to execute: {e:#}; \ trying protoc from PATH as fallback", protoc.display() ); break; } } } if !dir.pop() { break; } dir_rel.push(".."); } if check_protoc_good(Path::new("protoc")).is_ok() { return Ok(Some(PathBuf::from("protoc"))); } if is_github_actions() { return Err(anyhow::anyhow!( "`protoc` not found (checked $PROTOC env, bin/protoc, and PATH)" )); } eprintln!("`protoc` not found; likely it is missing in docker image"); Ok(None) }