ryo-server 0.1.0

[preview] RYO Server - tarpc-based RPC server for ryo operations
Documentation
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};

fn main() {
    let hash = source_hash();
    println!("cargo:rustc-env=RYO_COMMIT_HASH={}", hash);

    // Rebuild when any source file changes
    println!("cargo:rerun-if-changed=../");
}

/// Generate deterministic hash from all source files in crates/
fn source_hash() -> String {
    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    let crates_dir = Path::new(&manifest_dir).parent().unwrap(); // crates/

    let files = collect_source_files(crates_dir);

    let mut context = md5::Context::new();
    for file in &files {
        if let Ok(content) = std::fs::read(file) {
            // Include relative path to detect file renames/moves
            let rel_path = file.strip_prefix(crates_dir).unwrap_or(file);
            context.consume(rel_path.to_string_lossy().as_bytes());
            context.consume(&content);
        }
    }

    let digest = context.compute();
    format!("{:x}", digest)[..8].to_string()
}

/// Collect all .rs files and Cargo.toml files, sorted for determinism
fn collect_source_files(dir: &Path) -> BTreeSet<PathBuf> {
    let mut files = BTreeSet::new();
    collect_recursive(dir, &mut files);
    files
}

fn collect_recursive(dir: &Path, files: &mut BTreeSet<PathBuf>) {
    let Ok(entries) = std::fs::read_dir(dir) else {
        return;
    };

    for entry in entries.flatten() {
        let path = entry.path();

        // Skip target/ and hidden directories
        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
            if name.starts_with('.') || name == "target" {
                continue;
            }
        }

        if path.is_dir() {
            collect_recursive(&path, files);
        } else if let Some(ext) = path.extension() {
            if ext == "rs" || path.file_name().is_some_and(|n| n == "Cargo.toml") {
                files.insert(path);
            }
        } else if path.file_name().is_some_and(|n| n == "Cargo.toml") {
            files.insert(path);
        }
    }
}