razeri-plugin-contract 0.21.0

The dialect contract Razeri plugins are written against — rendering traits, data types, platform identity
Documentation
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

//! Fingerprints this crate's source.
//!
//! A crate's version moves when it is published, not every time its source
//! does, so two builds can carry the same version over different traits. A
//! plugin loaded from a directory must match the razeri that loads it exactly,
//! and this number is how the two tell such builds apart. It is read from the
//! files alone, so the same source gives the same number on every machine.

use std::path::{Path, PathBuf};

fn main() {
    println!("cargo::rerun-if-changed=src");
    let root = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").expect("set by cargo"));
    let fingerprint = source_fingerprint(&root.join("src"));
    println!("cargo::rustc-env=RAZERI_PLUGIN_CONTRACT_SOURCE={fingerprint:016x}");
}

/// FNV-1a over every file under `dir`, in the order of their paths relative
/// to it, each path followed by the file's bytes with carriage returns left
/// out, so a checkout that ends its lines with `\r\n` fingerprints the same.
fn source_fingerprint(dir: &Path) -> u64 {
    let mut files = Vec::new();
    collect(dir, dir, &mut files);
    files.sort();
    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
    let mut feed = |bytes: &[u8]| {
        for &byte in bytes {
            hash ^= u64::from(byte);
            hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
        }
    };
    for (relative, path) in files {
        feed(relative.as_bytes());
        feed(&[0]);
        let contents = std::fs::read(&path)
            .unwrap_or_else(|e| panic!("cannot read {} to fingerprint it: {e}", path.display()));
        let without_returns: Vec<u8> = contents.into_iter().filter(|&b| b != b'\r').collect();
        feed(&without_returns);
        feed(&[0]);
    }
    hash
}

/// Every file under `dir`, as its path relative to `root` spelled with `/`,
/// beside its full path.
fn collect(root: &Path, dir: &Path, files: &mut Vec<(String, PathBuf)>) {
    let entries = std::fs::read_dir(dir)
        .unwrap_or_else(|e| panic!("cannot read {} to fingerprint it: {e}", dir.display()));
    for entry in entries {
        let path = entry.expect("a directory entry").path();
        if path.is_dir() {
            collect(root, &path, files);
        } else {
            let relative = path.strip_prefix(root).expect("under the root");
            let spelled: Vec<String> = relative
                .components()
                .map(|c| c.as_os_str().to_string_lossy().into_owned())
                .collect();
            files.push((spelled.join("/"), path));
        }
    }
}