use std::collections::BTreeMap;
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
std::env::set_var("PROTOC", protoc_bin_vendored::protoc_bin_path()?);
}
let (proto_root, googleapis) = resolve_roots()?;
let protos = [
"udb/services/v1/data_broker.proto",
"udb/core/authn/services/v1/authn_service.proto",
"udb/core/authz/services/v1/authz_service.proto",
"udb/entity/v1/error.proto",
];
let proto_paths: Vec<PathBuf> = protos.iter().map(|p| proto_root.join(p)).collect();
for p in &proto_paths {
if !p.exists() {
return Err(format!(
"missing proto {}\nExpected the UDB protos under {}. In a repo checkout they live \
at ../../proto; for a published crate they must be vendored into sdk/rust/proto \
(see README, \"Publishing\").",
p.display(),
proto_root.display()
)
.into());
}
println!("cargo:rerun-if-changed={}", p.display());
}
println!("cargo:rerun-if-changed={}", proto_root.display());
let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
tonic_prost_build::configure()
.build_server(false)
.build_client(true)
.disable_comments(["google.api"])
.compile_protos(&proto_paths, &[proto_root.clone(), googleapis])?;
write_module_tree(&out_dir)?;
Ok(())
}
#[derive(Default)]
struct Node {
children: BTreeMap<String, Node>,
file: Option<String>,
}
fn write_module_tree(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let mut root = Node::default();
for entry in std::fs::read_dir(out_dir)? {
let path = entry?.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
let Some(pkg) = name.strip_suffix(".rs") else {
continue;
};
if pkg == MODULE_TREE_STEM || !pkg.contains('.') {
continue;
}
let mut node = &mut root;
for seg in pkg.split('.') {
node = node.children.entry(seg.to_string()).or_default();
}
node.file = Some(name.to_string());
}
let mut body = String::from("// @generated by build.rs from the emitted proto packages.\n");
render(&root, &mut body)?;
std::fs::write(out_dir.join(format!("{MODULE_TREE_STEM}.rs")), body)?;
Ok(())
}
const MODULE_TREE_STEM: &str = "udb_modules";
fn render(node: &Node, out: &mut String) -> Result<(), std::fmt::Error> {
if let Some(file) = &node.file {
writeln!(out, r#"include!(concat!(env!("OUT_DIR"), "/{file}"));"#)?;
}
for (name, child) in &node.children {
writeln!(out, "pub mod {name} {{")?;
render(child, out)?;
writeln!(out, "}}")?;
}
Ok(())
}
fn resolve_roots() -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>> {
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
let candidates = [
(
manifest.join("../../proto"),
manifest.join("../../third_party/googleapis"),
),
(
manifest.join("proto"),
manifest.join("third_party/googleapis"),
),
];
for (proto, google) in candidates {
if proto.is_dir() && google.is_dir() {
return Ok((normalize(&proto), normalize(&google)));
}
}
Err(
"could not locate the UDB protos: looked for ../../proto (repo checkout) and ./proto \
(vendored). See README, \"Publishing\"."
.into(),
)
}
fn normalize(p: &Path) -> PathBuf {
let Ok(canonical) = p.canonicalize() else {
return p.to_path_buf();
};
let text = canonical.to_string_lossy().into_owned();
if let Some(rest) = text.strip_prefix(r"\\?\UNC\") {
return PathBuf::from(format!(r"\\{rest}"));
}
if let Some(rest) = text.strip_prefix(r"\\?\") {
return PathBuf::from(rest);
}
canonical
}