dev-tools 0.9.9

Rust verification toolkit. Tests, benches, coverage, fuzz, security audit, dep hygiene, mutation, chaos, async, stress — one crate, opt in with feature flags.
Documentation
//! Build script. Parses `Cargo.lock` at compile time and emits a
//! `siblings.rs` snippet (in `OUT_DIR`) that the `dev` CLI binary
//! `include!`s as its `SIBLINGS` const-table.
//!
//! Removes the drift hazard of a hand-maintained version table — bumping
//! a sibling crate now updates `dev version`'s output automatically on
//! the next build, with no need to remember to touch `src/bin/dev.rs`.
//!
//! Degraded-mode behaviour: if `Cargo.lock` is missing or unparseable
//! (e.g. `cargo install` from a tarball that strangely lacked one), the
//! generated table uses `"0.0.0"` placeholders rather than aborting the
//! build. The CLI keeps working; the `dev version` output just shows
//! `0.0.0` for any sibling we couldn't resolve. The build never panics
//! on a weird package-manager edge case.

use std::env;
use std::fs;
use std::path::PathBuf;

/// Short alias + full crate name for every sibling. Order here is the
/// order `dev version` prints them in. Hand-maintained; safe because
/// the *order* doesn't change, only the version numbers do (and those
/// come from `Cargo.lock`).
const SIBLINGS: &[(&str, &str)] = &[
    ("report", "dev-report"),
    ("tools", "dev-tools"),
    ("fixtures", "dev-fixtures"),
    ("bench", "dev-bench"),
    ("async", "dev-async"),
    ("stress", "dev-stress"),
    ("chaos", "dev-chaos"),
    ("coverage", "dev-coverage"),
    ("security", "dev-security"),
    ("deps", "dev-deps"),
    ("ci", "dev-ci"),
    ("fuzz", "dev-fuzz"),
    ("flaky", "dev-flaky"),
    ("mutate", "dev-mutate"),
];

fn main() {
    println!("cargo:rerun-if-changed=Cargo.lock");
    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:rerun-if-changed=build.rs");

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
    let lock_path = PathBuf::from(&manifest_dir).join("Cargo.lock");

    let versions = fs::read_to_string(&lock_path)
        .ok()
        .map(|text| parse_versions(&text))
        .unwrap_or_default();

    let mut out = String::with_capacity(2048);
    out.push_str("// Auto-generated by build.rs from Cargo.lock — do not edit.\n");
    out.push_str("// Regenerated on every cargo build via the rerun-if-changed directives.\n\n");
    out.push_str("/// (alias, crate-name, version) for every sibling crate the CLI wraps.\n");
    out.push_str("/// Versions are pulled from Cargo.lock at compile time so the table\n");
    out.push_str("/// cannot drift away from what is actually linked.\n");
    out.push_str("pub const SIBLINGS: &[(&str, &str, &str)] = &[\n");
    for (alias, crate_name) in SIBLINGS {
        let version = lookup_version(&versions, crate_name).unwrap_or("0.0.0");
        out.push_str(&format!(
            "    ({:?}, {:?}, {:?}),\n",
            alias, crate_name, version
        ));
    }
    out.push_str("];\n");

    let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
    let dest = PathBuf::from(out_dir).join("siblings.rs");
    fs::write(&dest, out).expect("write siblings.rs");
}

/// Walk every `[[package]]` block in `Cargo.lock` and collect
/// `(name, version)` pairs. Lock-file format is line-oriented and
/// regular, so a hand-rolled scanner avoids pulling in a TOML parser
/// just for this.
fn parse_versions(text: &str) -> Vec<(String, String)> {
    let mut out = Vec::new();
    let mut pending_name: Option<String> = None;
    for raw in text.lines() {
        let line = raw.trim();
        if line == "[[package]]" {
            pending_name = None;
            continue;
        }
        if let Some(rest) = line.strip_prefix("name = \"") {
            if let Some(end) = rest.find('"') {
                pending_name = Some(rest[..end].to_string());
            }
            continue;
        }
        if let Some(rest) = line.strip_prefix("version = \"") {
            if let Some(end) = rest.find('"') {
                if let Some(name) = pending_name.take() {
                    out.push((name, rest[..end].to_string()));
                }
            }
        }
    }
    out
}

fn lookup_version<'a>(versions: &'a [(String, String)], name: &str) -> Option<&'a str> {
    versions
        .iter()
        .find(|(n, _)| n == name)
        .map(|(_, v)| v.as_str())
}