diff --git a/crates/oxide_cli/build.rs b/crates/oxide_cli/build.rs new file mode 100644 index 00000000..80ed0748 --- /dev/null +++ b/crates/oxide_cli/build.rs @@ -0,0 +1,24 @@ +//! 构建脚本:取构建期 git 短哈希注入二进制(`GIT_COMMIT` 环境变量)。 +//! +//! 哈希供 `--version` 自报,作为二进制新鲜度自证锚点(与 `git rev-parse --short HEAD` +//! 对照即可判定二进制是否落后于 HEAD)。无 git 环境(CI、解压源码)哈希缺省 +//! `unknown`,构建不失败。 + +fn main() { + // 构建脚本或源码变化时重跑,保持哈希与源码同步。 + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src"); + println!("cargo:rerun-if-changed=Cargo.toml"); + + // git 缺失或无提交时容缺,哈希缺省 "unknown"。 + let hash = std::process::Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|h| !h.is_empty()) + .unwrap_or_else(|| "unknown".to_string()); + + println!("cargo:rustc-env=GIT_COMMIT={hash}"); +} diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 2cb5645c..54e1c0c3 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -25,7 +25,12 @@ use oxide_vm::JsValue; mod bench; #[derive(Parser)] -#[command(name = "oxide", version, about = "OxideJS - Rust JavaScript engine")] +#[command( + name = "oxide", + // 版本号后附构建期 git 短哈希,`--version` 自报即新鲜度自证锚点。 + version = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_COMMIT"), ")"), + about = "OxideJS - Rust JavaScript engine" +)] struct Cli { #[command(subcommand)] command: Option, diff --git a/crates/oxide_test262/build.rs b/crates/oxide_test262/build.rs new file mode 100644 index 00000000..80ed0748 --- /dev/null +++ b/crates/oxide_test262/build.rs @@ -0,0 +1,24 @@ +//! 构建脚本:取构建期 git 短哈希注入二进制(`GIT_COMMIT` 环境变量)。 +//! +//! 哈希供 `--version` 自报,作为二进制新鲜度自证锚点(与 `git rev-parse --short HEAD` +//! 对照即可判定二进制是否落后于 HEAD)。无 git 环境(CI、解压源码)哈希缺省 +//! `unknown`,构建不失败。 + +fn main() { + // 构建脚本或源码变化时重跑,保持哈希与源码同步。 + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src"); + println!("cargo:rerun-if-changed=Cargo.toml"); + + // git 缺失或无提交时容缺,哈希缺省 "unknown"。 + let hash = std::process::Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|h| !h.is_empty()) + .unwrap_or_else(|| "unknown".to_string()); + + println!("cargo:rustc-env=GIT_COMMIT={hash}"); +} diff --git a/crates/oxide_test262/src/config.rs b/crates/oxide_test262/src/config.rs index dcc3c132..c9eabcd4 100644 --- a/crates/oxide_test262/src/config.rs +++ b/crates/oxide_test262/src/config.rs @@ -80,12 +80,13 @@ impl RunConfig { /// 打印用法说明。 pub(crate) fn usage() -> String { - "usage: test262-runner [--no-skip] [--no-regalloc] [--verbose] [--supervise] [--leak-check] [--leak-check-interval=N] [test262-root] [path-filter]\n\ + "usage: test262-runner [--no-skip] [--no-regalloc] [--verbose] [--supervise] [--leak-check] [--leak-check-interval=N] [--version] [test262-root] [path-filter]\n\ \n\ --no-skip Run capability-excluded tests and count unsupported compile/runtime results as failures.\n\ --no-regalloc Disable the liveness/precise-DCE/RegAlloc compiler chain (vregs stay as physical numbers).\n\ --verbose Print one PASS/FAIL/SKIP line per test (for on/off result-set comparison).\n\ --no-fail-list Do not print the per-path FAIL list at the end of the run.\n\ + --version Print the binary version with the build-time git short hash (freshness self-check), then exit.\n\ --supervise Run the suite as single-worker child-process windows with a hard per-test timeout and\n\ \x20 automatic resume past any hanging/crashing test. A hang or crash is reported by path.\n\ \x20 Combines with [path-filter]: the filter is applied per-test inside each child window.\n\ diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 4bd1bf2d..21e0e4d6 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -74,6 +74,11 @@ fn run_tests() -> bool { println!("{}", RunConfig::usage()); return true; } + // 版本自报走 stdout 并成功退出;哈希由构建期注入,无 git 环境缺省 "unknown"。 + if args.iter().any(|a| a == "--version") { + println!("test262-runner {} ({})", env!("CARGO_PKG_VERSION"), env!("GIT_COMMIT")); + return true; + } let config = match RunConfig::parse(&args) { Ok(config) => config, Err(msg) => { diff --git a/scripts/check_freshness.sh b/scripts/check_freshness.sh new file mode 100755 index 00000000..a395761c --- /dev/null +++ b/scripts/check_freshness.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# 二进制新鲜度检查:判定二进制是否落后于当前 HEAD。 +# +# 用法: scripts/check_freshness.sh [二进制路径 ...] +# 缺省检查 target/debug/oxide 与 target/debug/test262-runner。 +# +# 两条判据,均满足才判「新鲜」: +# 1. 二进制 --version 自报的 git 短哈希与 git rev-parse --short HEAD 一致; +# 2. 无源文件比二进制新(find crates third_party -name '*.rs' -newer 计数为 0)。 +# +# 退出码: 0 全部新鲜;1 任一 stale 或缺失。无 git 环境跳过判据 1(只查判据 2)。 + +set -u + +cd "$(dirname "$0")/.." + +bins=("$@") +if [ ${#bins[@]} -eq 0 ]; then + bins=(target/debug/oxide target/debug/test262-runner) +fi + +head_hash="$(git rev-parse --short HEAD 2>/dev/null || true)" + +stale=0 +for bin in "${bins[@]}"; do + if [ ! -x "$bin" ]; then + echo "stale: $bin (not built)" + stale=1 + continue + fi + + # 判据 1: 二进制自报哈希与 HEAD 一致(构建期无 git 的 "unknown" 视为不可自证)。 + if [ -n "$head_hash" ]; then + bin_hash="$(printf '%s\n' "$("$bin" --version 2>/dev/null | head -n1)" | grep -oE '\([0-9a-f]{7,}\)' | tr -d '()')" + if [ "$bin_hash" != "$head_hash" ]; then + echo "stale: $bin (self-reported ${bin_hash:-none}, HEAD $head_hash)" + stale=1 + continue + fi + fi + + # 判据 2: 无源文件比二进制新。 + newer="$(find crates third_party -name '*.rs' -newer "$bin" 2>/dev/null | wc -l)" + if [ "$newer" -gt 0 ]; then + echo "stale: $bin ($newer source file(s) newer than binary)" + stale=1 + continue + fi + + echo "fresh: $bin" +done + +exit "$stale"