shape-runtime 0.1.4

Bytecode compiler, builtins, and runtime infrastructure for Shape
Documentation
//! Build script for shape-runtime.
//!
//! Generates embedded stdlib source map used by the unified module loader so
//! `std::...` modules do not require runtime filesystem scanning.

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

fn main() {
    generate_embedded_stdlib_sources();
}

fn generate_embedded_stdlib_sources() {
    let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set");
    let dest_path = Path::new(&out_dir).join("embedded_stdlib_modules.rs");
    let stdlib_root = locate_stdlib_root();

    println!("cargo:rerun-if-changed={}", stdlib_root.display());
    emit_rerun_markers(&stdlib_root);

    let modules = collect_stdlib_modules(&stdlib_root);

    let mut generated = String::new();
    generated.push_str("// Auto-generated by shape-runtime/build.rs - DO NOT EDIT\n");
    generated.push_str("/// Embedded `std::...` modules (module path, source).\n");
    generated.push_str("pub const EMBEDDED_STDLIB_MODULES: &[(&str, &str)] = &[\n");

    for (module_path, source) in modules {
        generated.push_str(&format!("    ({:?}, {:?}),\n", module_path, source));
    }

    generated.push_str("];\n");

    fs::write(dest_path, generated).expect("failed to write embedded stdlib source map");
}

fn locate_stdlib_root() -> PathBuf {
    // Workspace builds use the canonical stdlib source of truth.
    let workspace_stdlib = Path::new("../shape-core/stdlib");
    if workspace_stdlib.is_dir() {
        return workspace_stdlib.to_path_buf();
    }

    // Published crate builds must be self-contained.
    let packaged_stdlib = Path::new("stdlib-src");
    if packaged_stdlib.is_dir() {
        return packaged_stdlib.to_path_buf();
    }

    panic!(
        "shape-runtime build could not find stdlib sources in '{}' or '{}'",
        workspace_stdlib.display(),
        packaged_stdlib.display()
    );
}

fn emit_rerun_markers(dir: &Path) {
    let Ok(entries) = fs::read_dir(dir) else {
        return;
    };

    for entry in entries.flatten() {
        let path = entry.path();
        println!("cargo:rerun-if-changed={}", path.display());
        if path.is_dir() {
            emit_rerun_markers(&path);
        }
    }
}

fn collect_stdlib_modules(stdlib_root: &Path) -> Vec<(String, String)> {
    let mut files = Vec::new();
    collect_shape_files_recursive(stdlib_root, &mut files);
    files.sort();

    files
        .into_iter()
        .filter_map(|path| {
            let rel = path.strip_prefix(stdlib_root).ok()?;
            let rel_no_ext = rel.with_extension("");
            let mut module_path = String::from("std");
            for segment in rel_no_ext.iter() {
                module_path.push_str("::");
                module_path.push_str(&segment.to_string_lossy());
            }
            let source = fs::read_to_string(&path).ok()?;
            Some((module_path, source))
        })
        .collect()
}

fn collect_shape_files_recursive(dir: &Path, out: &mut Vec<PathBuf>) {
    let Ok(entries) = fs::read_dir(dir) else {
        return;
    };

    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            collect_shape_files_recursive(&path, out);
            continue;
        }
        if path.extension().and_then(|s| s.to_str()) == Some("shape") {
            out.push(path);
        }
    }
}