docs/ci: selfhost bootstrap/exe-first workflows; add ny-llvmc scaffolding + JSON v0 schema validation; plan: unify to Nyash ABI v2 (no backwards compat)
This commit is contained in:
55
src/runner/modes/common_util/io.rs
Normal file
55
src/runner/modes/common_util/io.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::io::Read;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct ChildOutput {
|
||||
pub stdout: Vec<u8>,
|
||||
pub stderr: Vec<u8>,
|
||||
pub status_ok: bool,
|
||||
pub exit_code: Option<i32>,
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
/// Spawn command with timeout (ms), capture stdout/stderr, and return ChildOutput.
|
||||
pub fn spawn_with_timeout(mut cmd: Command, timeout_ms: u64) -> std::io::Result<ChildOutput> {
|
||||
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
let mut child = cmd.spawn()?;
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
let start = Instant::now();
|
||||
let mut timed_out = false;
|
||||
let mut exit_status: Option<std::process::ExitStatus> = None;
|
||||
loop {
|
||||
match child.try_wait()? {
|
||||
Some(status) => { exit_status = Some(status); break },
|
||||
None => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut out_buf = Vec::new();
|
||||
let mut err_buf = Vec::new();
|
||||
if let Some(mut s) = ch_stdout {
|
||||
let _ = s.read_to_end(&mut out_buf);
|
||||
}
|
||||
if let Some(mut s) = ch_stderr {
|
||||
let _ = s.read_to_end(&mut err_buf);
|
||||
}
|
||||
let (status_ok, exit_code) = if let Some(st) = exit_status {
|
||||
(st.success(), st.code())
|
||||
} else { (false, None) };
|
||||
Ok(ChildOutput {
|
||||
stdout: out_buf,
|
||||
stderr: err_buf,
|
||||
status_ok,
|
||||
exit_code,
|
||||
timed_out,
|
||||
})
|
||||
}
|
||||
9
src/runner/modes/common_util/mod.rs
Normal file
9
src/runner/modes/common_util/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Shared helpers for runner/modes/common.rs
|
||||
*
|
||||
* Minimal extraction to reduce duplication and prepare for full split.
|
||||
*/
|
||||
|
||||
pub mod pyvm;
|
||||
pub mod selfhost_exe;
|
||||
pub mod io;
|
||||
39
src/runner/modes/common_util/pyvm.rs
Normal file
39
src/runner/modes/common_util/pyvm.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::process::Stdio;
|
||||
|
||||
/// Run PyVM harness over a MIR module, returning the exit code
|
||||
pub fn run_pyvm_harness(module: &crate::mir::MirModule, tag: &str) -> Result<i32, String> {
|
||||
let py3 = which::which("python3").map_err(|e| format!("python3 not found: {}", e))?;
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if !runner.exists() {
|
||||
return Err(format!("PyVM runner not found: {}", runner.display()));
|
||||
}
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json");
|
||||
crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, &mir_json_path)
|
||||
.map_err(|e| format!("PyVM MIR JSON emit error: {}", e))?;
|
||||
crate::cli_v!("[ny-compiler] using PyVM ({} ) → {}", tag, mir_json_path.display());
|
||||
// Determine entry function hint (prefer Main.main if present)
|
||||
let entry = if module.functions.contains_key("Main.main") {
|
||||
"Main.main"
|
||||
} else if module.functions.contains_key("main") {
|
||||
"main"
|
||||
} else {
|
||||
"Main.main"
|
||||
};
|
||||
let status = std::process::Command::new(py3)
|
||||
.args([
|
||||
runner.to_string_lossy().as_ref(),
|
||||
"--in",
|
||||
&mir_json_path.display().to_string(),
|
||||
"--entry",
|
||||
entry,
|
||||
])
|
||||
.status()
|
||||
.map_err(|e| format!("spawn pyvm: {}", e))?;
|
||||
let code = status.code().unwrap_or(1);
|
||||
if !status.success() {
|
||||
crate::cli_v!("❌ PyVM ({}) failed (status={})", tag, code);
|
||||
}
|
||||
Ok(code)
|
||||
}
|
||||
131
src/runner/modes/common_util/selfhost_exe.rs
Normal file
131
src/runner/modes/common_util/selfhost_exe.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use std::io::Read;
|
||||
use std::process::Stdio;
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Try external selfhost compiler EXE to parse Ny -> JSON v0 and return MIR module.
|
||||
/// Returns Some(module) on success, None on failure (timeout/invalid output/missing exe)
|
||||
pub fn exe_try_parse_json_v0(filename: &str, timeout_ms: u64) -> Option<crate::mir::MirModule> {
|
||||
// Resolve parser EXE path
|
||||
let exe_path = if let Ok(p) = std::env::var("NYASH_NY_COMPILER_EXE_PATH") {
|
||||
std::path::PathBuf::from(p)
|
||||
} else {
|
||||
let mut p = std::path::PathBuf::from("dist/nyash_compiler");
|
||||
#[cfg(windows)]
|
||||
{
|
||||
p.push("nyash_compiler.exe");
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
p.push("nyash_compiler");
|
||||
}
|
||||
if !p.exists() {
|
||||
if let Ok(w) = which::which("nyash_compiler") {
|
||||
w
|
||||
} else {
|
||||
p
|
||||
}
|
||||
} else {
|
||||
p
|
||||
}
|
||||
};
|
||||
if !exe_path.exists() {
|
||||
crate::cli_v!("[ny-compiler] exe not found at {}", exe_path.display());
|
||||
return None;
|
||||
}
|
||||
// Build command
|
||||
let mut cmd = std::process::Command::new(&exe_path);
|
||||
cmd.arg(filename);
|
||||
if crate::config::env::ny_compiler_min_json() {
|
||||
cmd.arg("--min-json");
|
||||
}
|
||||
if crate::config::env::selfhost_read_tmp() {
|
||||
cmd.arg("--read-tmp");
|
||||
}
|
||||
if let Some(raw) = crate::config::env::ny_compiler_child_args() {
|
||||
for tok in raw.split_whitespace() {
|
||||
cmd.arg(tok);
|
||||
}
|
||||
}
|
||||
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] exe spawn failed: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
let start = Instant::now();
|
||||
let mut timed_out = false;
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_)) => break,
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(10));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] exe wait error: {}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut out_buf = Vec::new();
|
||||
let mut err_buf = Vec::new();
|
||||
if let Some(mut s) = ch_stdout {
|
||||
let _ = s.read_to_end(&mut out_buf);
|
||||
}
|
||||
if let Some(mut s) = ch_stderr {
|
||||
let _ = s.read_to_end(&mut err_buf);
|
||||
}
|
||||
if timed_out {
|
||||
let head = String::from_utf8_lossy(&out_buf)
|
||||
.chars()
|
||||
.take(200)
|
||||
.collect::<String>();
|
||||
eprintln!(
|
||||
"[ny-compiler] exe timeout after {} ms; stdout(head)='{}'",
|
||||
timeout_ms,
|
||||
head.replace('\n', "\\n")
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let stdout = match String::from_utf8(out_buf) {
|
||||
Ok(s) => s,
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
let mut json_line = String::new();
|
||||
for line in stdout.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") {
|
||||
json_line = t.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if json_line.is_empty() {
|
||||
if crate::config::env::cli_verbose() {
|
||||
let head: String = stdout.chars().take(200).collect();
|
||||
let errh: String = String::from_utf8_lossy(&err_buf).chars().take(200).collect();
|
||||
crate::cli_v!(
|
||||
"[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'",
|
||||
head.replace('\n', "\\n"),
|
||||
errh.replace('\n', "\\n")
|
||||
);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
match crate::runner::json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => Some(module),
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] JSON parse failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user