vox-lang 0.4.4

A systems level compiler for Vox (sentence based code)
//! Embeds `coreasm/` into the compiler binary.
//!
//! `cargo install` copies only the binary into `~/.cargo/bin`, leaving the
//! crate's `coreasm/` behind in the registry cache — so a cargo-installed vox
//! has no runtime to emit against and cannot compile anything. The crate
//! already ships every `.asm` file (they are inside the crates.io tarball and
//! covered by its checksum), so the fix needs no network: carry them in the
//! binary and write them out on first use. See docs/plans/312.
//!
//! The file list is walked from disk at build time rather than hand-written,
//! so adding a `.asm` file cannot silently fail to ship.

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

fn collect(root: &Path, dir: &Path, found: &mut Vec<(String, PathBuf)>) {
    let entries = match fs::read_dir(dir) {
        Ok(entries) => entries,
        Err(e) => panic!("build.rs: cannot read {}: {e}", dir.display()),
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            collect(root, &path, found);
        } else if path.extension().and_then(|e| e.to_str()) == Some("asm") {
            let rel = path
                .strip_prefix(root)
                .expect("walked path is under root")
                .to_str()
                .expect("coreasm paths are UTF-8")
                // The relative path is used as a key and rebuilt with
                // Path::join on the far side, so keep it separator-neutral.
                .replace('\\', "/");
            found.push((rel, path));
        }
    }
}

fn main() {
    let manifest_dir = PathBuf::from(
        env::var("CARGO_MANIFEST_DIR").expect("cargo always sets CARGO_MANIFEST_DIR"),
    );
    let coreasm = manifest_dir.join("coreasm");

    println!("cargo:rerun-if-changed=coreasm");

    let mut found = Vec::new();
    collect(&coreasm, &coreasm, &mut found);
    if found.is_empty() {
        panic!(
            "build.rs: no .asm files under {} — the crate must ship coreasm",
            coreasm.display()
        );
    }
    // Sorted so the generated source is byte-identical between builds of the
    // same tree; readdir order is not guaranteed.
    found.sort();

    let mut generated = String::from(
        "// Generated by build.rs from coreasm/. Do not edit.\n\
         pub static EMBEDDED_COREASM: &[(&str, &[u8])] = &[\n",
    );
    for (rel, abs) in &found {
        // Rust's Debug for str emits a correctly escaped string literal, so
        // paths containing quotes or backslashes cannot break the output.
        generated.push_str(&format!(
            "    ({:?}, include_bytes!({:?})),\n",
            rel,
            abs.to_str().expect("coreasm paths are UTF-8")
        ));
        println!("cargo:rerun-if-changed={}", abs.display());
    }
    generated.push_str("];\n");

    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("cargo always sets OUT_DIR"));
    fs::write(out_dir.join("embedded_coreasm.rs"), generated)
        .expect("build.rs: cannot write into OUT_DIR");
}