Lines
94.2 %
Functions
80 %
Branches
100 %
use std::path::Path;
use std::process::Command;
fn main() {
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.expect("Failed to get git revision");
let revision = String::from_utf8(output.stdout)
.expect("Invalid UTF-8 in git output")
.trim()
.to_owned();
println!("cargo:rustc-env=GIT_REVISION={revision}");
println!("cargo:rerun-if-changed=.git/HEAD");
tangle_entity_registry();
}
/// Tangles `doc/scripting/entity_registry.org` →
/// `src/compiler/context/entity_registry.rs`. Mirrors the
/// `rpc/build.rs` + `scripting/format/build.rs` precedent: the org
/// file holds the per-entity field-layout table; the babel block
/// emits Rust during `cargo build`. Adding a new entity kind =
/// editing one row in the org; cargo regenerates.
fn tangle_entity_registry() {
let org_path = "../../doc/scripting/entity_registry.org";
println!("cargo:rerun-if-changed={org_path}");
// Two tangle blocks share the org file: one emits the
// ENTITY_SPECS const consumed by `new_skeleton` /
// `register_entity_allocators`; the other emits the typed-entity
// accessor natives. Both walk the same per-field rows so the
// struct layout, allocator signature, and accessor surface stay
// in lockstep — adding a field is one row, cargo regenerates
// every consumer.
run_emacs_block(org_path, "emit-rust-specs");
run_emacs_block(org_path, "emit-rust-accessors");
run_emacs_block(org_path, "emit-rust-decode-layout");
// Builtin symbol-table name lists tangle from a separate org
// (the builtin reference doc holds the canonical operator /
// special-form / native registry).
let builtins_org = "../../doc/scripting/builtin_reference.org";
println!("cargo:rerun-if-changed={builtins_org}");
run_emacs_block(builtins_org, "emit-builtin-names");
for path in [
"src/compiler/context/entity_registry.rs",
"src/compiler/native/typed_entity.rs",
"src/runtime/entity_layout.rs",
"src/runtime/symbol/builtins_generated.rs",
] {
rustfmt_generated(path);
/// Formats a generated source so it matches `cargo fmt --all --check`.
/// rustfmt is resolved from the active toolchain's sysroot (where
/// `cargo fmt` finds it), not bare `$PATH`: minimal CI images expose
/// rustfmt only in the sysroot bin, so `Command::new("rustfmt")` would
/// spawn-fail there and ship the file unformatted, breaking the later
/// fmt check. A genuine failure is surfaced as a build warning, not
/// silently swallowed.
fn rustfmt_generated(path: &str) {
match rustfmt_command().arg("--edition=2024").arg(path).status() {
Ok(s) if s.success() => {}
Ok(s) => println!("cargo:warning=rustfmt exited {s} on {path}"),
Err(e) => println!("cargo:warning=could not run rustfmt on {path}: {e}"),
fn rustfmt_command() -> Command {
// Probe the compiler Cargo set for this build (`$RUSTC`), not bare
// `rustc`, so the sysroot matches the active toolchain.
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_owned());
let sysroot_rustfmt = Command::new(rustc)
.args(["--print", "sysroot"])
.ok()
.filter(|out| out.status.success())
.map(|out| Path::new(String::from_utf8_lossy(&out.stdout).trim()).join("bin/rustfmt"))
.filter(|path| path.exists());
sysroot_rustfmt.map_or_else(|| Command::new("rustfmt"), Command::new)
fn run_emacs_block(org_path: &str, block_name: &str) {
let status = Command::new("emacs")
.args([
"-q",
"--batch",
org_path,
"--eval",
"(setq org-confirm-babel-evaluate nil create-lockfiles nil)",
&format!("(org-babel-goto-named-src-block {block_name:?})"),
"(org-babel-execute-src-block)",
"-f",
"kill-emacs",
])
.status();
match status {
Ok(s) => panic!(
"emacs --batch tangle of {org_path} block {block_name:?} \
exited with {s} — regen failed"
),
Err(e) => panic!(
"failed to spawn emacs for {org_path} block {block_name:?} \
tangle: {e}. cargo build requires emacs on PATH"