use std::env; use std::fs; use std::path::{Path, PathBuf}; fn main() -> Result<(), Box> { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); let proto_root = manifest_dir.join("proto"); println!("cargo:rerun-if-changed=build.rs"); if !proto_root.is_dir() { println!("cargo:warning=proto 目录不存在,跳过协议生成"); return Ok(()); } println!("cargo:rerun-if-changed={}", proto_root.display()); let sources = collect_protos(&proto_root)?; if sources.is_empty() { println!("cargo:warning=proto 下没有 .proto 文件,跳过协议生成"); return Ok(()); } let cwd = env::current_dir()?; let include_root = relative_path(&cwd, &proto_root); let protos: Vec = sources .iter() .map(|path| relative_path(&cwd, path)) .collect(); tonic_prost_build::configure() .build_server(false) .build_client(true) .compile_protos(&protos, &[include_root])?; Ok(()) } fn collect_protos(dir: &Path) -> std::io::Result> { let mut found = Vec::new(); let mut stack = vec![dir.to_path_buf()]; while let Some(dir) = stack.pop() { for entry in fs::read_dir(&dir)? { let path = entry?.path(); if path.is_dir() { stack.push(path); } else if path.extension().is_some_and(|ext| ext == "proto") { found.push(path); } } } found.sort(); Ok(found) } fn relative_path(base: &Path, target: &Path) -> PathBuf { let base: Vec<_> = base.components().collect(); let target: Vec<_> = target.components().collect(); let common = base .iter() .zip(target.iter()) .take_while(|(a, b)| a == b) .count(); if common == 0 { return target.iter().collect(); } let mut rel = PathBuf::new(); for _ in common..base.len() { rel.push(".."); } rel.extend(target[common..].iter().map(|c| c.as_os_str())); rel }