mkit-rpc 0.5.0

Versioned wire protocols for mkit cross-system speech: external signers (signer.proto), SSH transport frames (ssh.proto), shared types (common.proto). Schemas defined in protobuf via buffa, with length-prefixed framing.
Documentation
// Stages the buffa-generated Rust modules for the .proto schemas in
// proto/ into $OUT_DIR.
//
// Default path: copy the pre-generated sources committed under
// generated/ — no protoc required. This keeps docs.rs (whose build
// image ships protoc 3.21, too old for the `edition = "2023"` protos)
// and stock-distro users building, with zero system dependencies.
//
// Regeneration path: set MKIT_RPC_CODEGEN=1 to run buffa-build against
// proto/ instead (requires protoc >= 27 on PATH or via PROTOC). After
// changing a .proto, run scripts/regen-rpc-proto.sh from the repo root
// to refresh generated/ and commit the result.

use std::path::PathBuf;

fn main() {
    #[cfg(feature = "transport")]
    transport::stage();
    #[cfg(feature = "hooks")]
    hooks::stage();
    let proto_dir = PathBuf::from("proto");
    // Shared ref types (mkit.common.v1.RefExpectation / RefEntry), one repo
    // root up from rust/crates/mkit-rpc — see mkit/common/v1/refs.proto's
    // header comment. Also used by apps/repo-worker's repo.proto.
    let common_dir = PathBuf::from("../../../proto");
    let common_refs_proto = common_dir.join("mkit/common/v1/refs.proto");

    let files = [
        proto_dir.join("mkit/rpc/v1/common.proto"),
        proto_dir.join("mkit/rpc/v1/signer/signer.proto"),
        proto_dir.join("mkit/rpc/v1/ssh/ssh.proto"),
        proto_dir.join("mkit/rpc/v1/verify/verify.proto"),
        common_refs_proto.clone(),
    ];

    // Re-run when a .proto, the vendored output, or the mode changes.
    // Without these, cargo re-runs build.rs on every recompile.
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=generated");
    for f in &files {
        println!("cargo:rerun-if-changed={}", f.display());
    }
    println!("cargo:rerun-if-env-changed=PROTOC");
    println!("cargo:rerun-if-env-changed=MKIT_RPC_CODEGEN");

    let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"));
    // Marker distinguishing real codegen output from staged copies of
    // generated/ — both fill OUT_DIR with the same .rs file set, so
    // scripts/regen-rpc-proto.sh needs this to find the right dir.
    let marker = out_dir.join(".mkit-rpc-codegen");

    if std::env::var_os("MKIT_RPC_CODEGEN").is_some() {
        buffa_build::Config::new()
            .files(&files)
            .includes(&[&proto_dir, &common_dir])
            .include_file("_includes.rs")
            // Emits `arbitrary::Arbitrary` derives gated behind the
            // crate's opt-in `arbitrary` feature — used by the fuzz
            // harness (rust/fuzz) for decode/roundtrip targets.
            .generate_arbitrary(true)
            .compile()
            .expect("buffa codegen");
        std::fs::write(&marker, b"").expect("write codegen marker");
        return;
    }
    // A previous codegen-mode run may have used this same OUT_DIR;
    // drop its marker so the regen script never copies staged files.
    let _ = std::fs::remove_file(&marker);
    let vendored = PathBuf::from("generated");
    let mut staged = 0usize;
    for entry in std::fs::read_dir(&vendored).expect("read generated/") {
        let entry = entry.expect("read generated/ entry");
        let path = entry.path();
        if path.extension().is_some_and(|e| e == "rs") {
            std::fs::copy(&path, out_dir.join(entry.file_name())).expect("stage generated module");
            staged += 1;
        }
    }
    assert!(
        staged > 0,
        "generated/ contains no .rs modules — run scripts/regen-rpc-proto.sh"
    );
}

#[cfg(feature = "hooks")]
mod hooks {
    use std::path::{Path, PathBuf};

    /// Written next to real codegen output so the regen script can tell it
    /// from a staged copy of `generated/hooks/`.
    const MARKER: &str = ".mkit-rpc-hooks-codegen";

    pub(crate) fn stage() {
        let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR")).join("hooks");
        std::fs::create_dir_all(&out_dir).expect("create hooks OUT_DIR");
        println!("cargo:rerun-if-changed=generated/hooks");
        println!("cargo:rerun-if-env-changed=PROTOC");
        println!("cargo:rerun-if-env-changed=MKIT_HOOKS_CODEGEN");
        let marker = out_dir.join(MARKER);

        if std::env::var_os("MKIT_HOOKS_CODEGEN").is_some() {
            let root = Path::new(env!("CARGO_MANIFEST_DIR"))
                .join("../../../proto")
                .canonicalize()
                .expect("canonical proto root not found: expected proto/ at repo root");
            let hooks = root.join("mkit/server/hooks/v1/hooks.proto");
            println!("cargo:rerun-if-changed={}", hooks.display());
            buffa_build::Config::new()
                .files(&[hooks.to_str().expect("proto path is valid UTF-8")])
                .includes(&[root.to_str().expect("proto root is valid UTF-8")])
                .out_dir(&out_dir)
                .generate_views(false)
                .generate_json(true)
                .message_attribute(".mkit.server.hooks.v1.AdmitRequest", "#[doc = \"WARNING: Contains payment credentials. Never log or Debug-print this message or its credential_headers. Build with ..Default::default() for additive evolution.\"]")
                .include_file("_hooks.rs")
                .compile()
                .expect("buffa codegen failed for the canonical hooks proto");
            std::fs::write(&marker, b"").expect("write codegen marker");
            return;
        }

        let _ = std::fs::remove_file(&marker);
        let mut staged = 0usize;
        for entry in std::fs::read_dir(Path::new("generated/hooks")).expect("read generated/hooks/")
        {
            let path = entry.expect("read generated/hooks/ entry").path();
            if path.extension().is_some_and(|e| e == "rs") {
                let name = path.file_name().expect("file name");
                std::fs::copy(&path, out_dir.join(name)).expect("stage generated module");
                staged += 1;
            }
        }
        assert!(
            staged > 0,
            "generated/hooks/ contains no .rs modules: run scripts/regen-hooks-proto.sh"
        );
    }
}

#[cfg(feature = "transport")]
mod transport {
    use std::path::{Path, PathBuf};

    /// Written next to real codegen output, so the regen script can tell it
    /// from a staged copy of `generated/` (both fill `OUT_DIR` with the same
    /// file set).
    const MARKER: &str = ".mkit-rpc-transport-codegen";

    pub(crate) fn stage() {
        let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"));
        println!("cargo:rerun-if-changed=generated/transport");
        println!("cargo:rerun-if-env-changed=PROTOC");
        println!("cargo:rerun-if-env-changed=MKIT_TRANSPORT_CODEGEN");
        let marker = out_dir.join(MARKER);

        if std::env::var_os("MKIT_TRANSPORT_CODEGEN").is_some() {
            // The repo-root canonical proto module, three hops up
            // (rust/crates/mkit-rpc -> repo root).
            let root = Path::new(env!("CARGO_MANIFEST_DIR"))
                .join("../../../proto")
                .canonicalize()
                .expect("canonical proto root not found: expected proto/ at repo root");
            let transport = root.join("mkit/transport/v1/transport.proto");
            let health = root.join("grpc/health/v1/health.proto");
            println!("cargo:rerun-if-changed={}", transport.display());
            println!("cargo:rerun-if-changed={}", health.display());
            connectrpc_build::Config::new()
                .files(&[
                    transport.to_str().expect("proto path is valid UTF-8"),
                    health.to_str().expect("proto path is valid UTF-8"),
                ])
                .includes(&[root.to_str().expect("proto root is valid UTF-8")])
                .include_file("_connectrpc.rs")
                .compile()
                .expect("connectrpc-build codegen failed for the canonical protos");
            std::fs::write(&marker, b"").expect("write codegen marker");
            return;
        }

        let _ = std::fs::remove_file(&marker);
        let mut staged = 0usize;
        for entry in
            std::fs::read_dir(Path::new("generated/transport")).expect("read generated/transport/")
        {
            let path = entry.expect("read generated/transport/ entry").path();
            if path.extension().is_some_and(|e| e == "rs") {
                let name = path.file_name().expect("file name");
                std::fs::copy(&path, out_dir.join(name)).expect("stage generated module");
                staged += 1;
            }
        }
        assert!(
            staged > 0,
            "generated/transport/ contains no .rs modules: run scripts/regen-transport-proto.sh"
        );
    }
}