volt-client-grpc 0.0.3

TDX Volt gRPC client library for Rust
Documentation
//! Build script for volt-client-grpc
//!
//! This build script compiles .proto files from the tdxvolt-core repository
//! using tonic-build to generate Rust code for gRPC services and messages.

use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Get the directory containing Cargo.toml (the package root)
    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);

    let protoc_path = protoc_bin_vendored::protoc_bin_path()?;

    std::env::set_var("PROTOC", protoc_path);

    // Preferred proto locations (first match wins)
    let mut proto_root = None;

    let local_proto = manifest_dir.join("proto").join("volt_api");
    if local_proto.exists() {
        proto_root = Some(local_proto);
    } else {
        // Legacy layout: sibling tdxvolt-core repository
        let legacy_proto = manifest_dir
            .join("..") // packages
            .join("..") // project root
            .join("..") // Documents
            .join("tdxvolt-core")
            .join("src")
            .join("volt_api");

        if legacy_proto.exists() {
            proto_root = Some(legacy_proto);
        }
    }

    let proto_root = match proto_root {
        Some(path) => path.canonicalize()?,
        None => {
            eprintln!("Warning: Proto directory not found. Looked in:");
            eprintln!("  - {:?}", manifest_dir.join("proto").join("volt_api"));
            eprintln!(
                "  - {:?}",
                manifest_dir
                    .join("..")
                    .join("..")
                    .join("..")
                    .join("tdxvolt-core")
                    .join("src")
                    .join("volt_api")
            );
            eprintln!("CARGO_MANIFEST_DIR: {:?}", manifest_dir);
            eprintln!("Skipping proto generation, using manual proto definitions.");
            return Ok(());
        }
    };
    println!("cargo:rerun-if-changed={}", proto_root.display());
    eprintln!("Proto root: {:?}", proto_root);

    // Collect all .proto files
    let proto_files = collect_proto_files(&proto_root)?;

    if proto_files.is_empty() {
        eprintln!("Warning: No .proto files found in {:?}", proto_root);
        return Ok(());
    }

    eprintln!("Found {} proto files", proto_files.len());
    for f in &proto_files {
        eprintln!("  - {}", f.display());
        println!("cargo:rerun-if-changed={}", f.display());
    }

    // Configure tonic-prost-build
    tonic_prost_build::configure()
        // Only generate client code, not server
        .build_server(false)
        .build_client(true)
        // Disable generation of the transport connect methods to avoid conflict
        // with the `Connect` RPC method on VoltAPI service.
        // Clients can construct the transport manually using tonic::transport::Channel
        .build_transport(false)
        // Use the BTreeMap for map fields for deterministic ordering
        .btree_map(".")
        // Add serde support for JSON serialization/deserialization
        .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
        .type_attribute(".", "#[serde(rename_all = \"snake_case\")]")
        // Compile all proto files
        .compile_protos(
            &proto_files,
            &[proto_root], // Include paths for imports
        )?;

    eprintln!("Proto compilation successful!");

    Ok(())
}

/// Recursively collect all .proto files in a directory
fn collect_proto_files(dir: &PathBuf) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
    let mut proto_files = Vec::new();

    if !dir.is_dir() {
        return Ok(proto_files);
    }

    for entry in std::fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            proto_files.extend(collect_proto_files(&path)?);
        } else if path.extension().map(|e| e == "proto").unwrap_or(false) {
            proto_files.push(path);
        }
    }

    Ok(proto_files)
}