docs: add --build (MVP) usage to READMEs; guide updated with WSL tips. cli/runner: wire minimal --build to produce EXE (plugins→core→AOT→link).
This commit is contained in:
25
README.ja.md
25
README.ja.md
@ -184,6 +184,31 @@ smoke_obj_array = "NYASH_LLVM_OBJ_OUT={root}/nyash_llvm_temp.o ./target/release/
|
||||
|
||||
---
|
||||
|
||||
## 🧰 一発ビルド(MVP): `nyash --build`
|
||||
|
||||
`nyash.toml` を読み、プラグイン → コア → AOT → リンクまでを一発実行する最小ビルド機能です。
|
||||
|
||||
基本(Cranelift AOT)
|
||||
```bash
|
||||
./target/release/nyash --build nyash.toml \
|
||||
--app apps/egui-hello-plugin/main.nyash \
|
||||
--out app_egui
|
||||
```
|
||||
|
||||
主なオプション(最小)
|
||||
- `--build <path>`: nyash.toml の場所
|
||||
- `--app <file>`: エントリ `.nyash`
|
||||
- `--out <name>`: 出力EXE名(既定: `app`/`app.exe`)
|
||||
- `--build-aot cranelift|llvm`(既定: cranelift)
|
||||
- `--profile release|debug`(既定: release)
|
||||
- `--target <triple>`(必要時のみ)
|
||||
|
||||
注意
|
||||
- LLVM AOT には LLVM 18 が必要(`LLVM_SYS_180_PREFIX` を設定)。
|
||||
- GUIを含む場合、AOTのオブジェクト出力時にウィンドウが一度開きます(閉じて続行)。
|
||||
- WSL で表示されない場合は `docs/guides/cranelift_aot_egui_hello.md` のWSL Tips(Wayland→X11切替)を参照。
|
||||
|
||||
|
||||
## 📊 **パフォーマンスベンチマーク**
|
||||
|
||||
実世界ベンチマーク結果 (ny_bench.nyash):
|
||||
|
||||
25
README.md
25
README.md
@ -160,6 +160,31 @@ cargo build --release --features wasm-backend
|
||||
|
||||
---
|
||||
|
||||
## 🧰 One‑Command Build (MVP): `nyash --build`
|
||||
|
||||
Reads `nyash.toml`, builds plugins → core → emits AOT object → links an executable in one shot.
|
||||
|
||||
Basic (Cranelift AOT)
|
||||
```bash
|
||||
./target/release/nyash --build nyash.toml \
|
||||
--app apps/egui-hello-plugin/main.nyash \
|
||||
--out app_egui
|
||||
```
|
||||
|
||||
Key options (minimal)
|
||||
- `--build <path>`: path to nyash.toml
|
||||
- `--app <file>`: entry `.nyash`
|
||||
- `--out <name>`: output executable (default: `app`/`app.exe`)
|
||||
- `--build-aot cranelift|llvm` (default: cranelift)
|
||||
- `--profile release|debug` (default: release)
|
||||
- `--target <triple>` (only when needed)
|
||||
|
||||
Notes
|
||||
- LLVM AOT requires LLVM 18 (`LLVM_SYS_180_PREFIX`).
|
||||
- Apps that open a GUI may show a window during AOT emission; close it to continue.
|
||||
- On WSL if the window doesn’t show, see `docs/guides/cranelift_aot_egui_hello.md` (Wayland→X11).
|
||||
|
||||
|
||||
## 📊 **Performance Benchmarks**
|
||||
|
||||
Real-world benchmark results (ny_bench.nyash):
|
||||
|
||||
50
src/cli.rs
50
src/cli.rs
@ -57,6 +57,13 @@ pub struct CliConfig {
|
||||
// Phase-15: JSON IR v0 bridge
|
||||
pub ny_parser_pipe: bool,
|
||||
pub json_file: Option<String>,
|
||||
// Build system (MVP)
|
||||
pub build_path: Option<String>,
|
||||
pub build_app: Option<String>,
|
||||
pub build_out: Option<String>,
|
||||
pub build_aot: Option<String>,
|
||||
pub build_profile: Option<String>,
|
||||
pub build_target: Option<String>,
|
||||
}
|
||||
|
||||
impl CliConfig {
|
||||
@ -317,6 +324,43 @@ impl CliConfig {
|
||||
.help("Opt-in: read [ny_plugins] from nyash.toml and load scripts in order")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
// Build system (MVP)
|
||||
.arg(
|
||||
Arg::new("build")
|
||||
.long("build")
|
||||
.value_name("PATH")
|
||||
.help("Build AOT executable using nyash.toml at PATH (MVP)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build-app")
|
||||
.long("app")
|
||||
.value_name("FILE")
|
||||
.help("Entry Nyash script for --build (e.g., apps/hello/main.nyash)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build-out")
|
||||
.long("out")
|
||||
.value_name("FILE")
|
||||
.help("Output executable name for --build (default: app/app.exe)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build-aot")
|
||||
.long("build-aot")
|
||||
.value_name("{cranelift|llvm}")
|
||||
.help("AOT backend for --build (default: cranelift)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build-profile")
|
||||
.long("profile")
|
||||
.value_name("{release|debug}")
|
||||
.help("Cargo profile for --build (default: release)")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build-target")
|
||||
.long("target")
|
||||
.value_name("TRIPLE")
|
||||
.help("Target triple for --build (e.g., x86_64-pc-windows-msvc)")
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert ArgMatches to CliConfig
|
||||
@ -361,6 +405,12 @@ impl CliConfig {
|
||||
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
|
||||
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
|
||||
json_file: matches.get_one::<String>("json-file").cloned(),
|
||||
build_path: matches.get_one::<String>("build").cloned(),
|
||||
build_app: matches.get_one::<String>("build-app").cloned(),
|
||||
build_out: matches.get_one::<String>("build-out").cloned(),
|
||||
build_aot: matches.get_one::<String>("build-aot").cloned(),
|
||||
build_profile: matches.get_one::<String>("build-profile").cloned(),
|
||||
build_target: matches.get_one::<String>("build-target").cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,14 @@ impl NyashRunner {
|
||||
|
||||
/// Run Nyash based on the configuration
|
||||
pub fn run(&self) {
|
||||
// Build system (MVP): nyash --build <nyash.toml>
|
||||
if let Some(cfg_path) = self.config.build_path.clone() {
|
||||
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
||||
eprintln!("❌ build error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Phase-15: JSON IR v0 bridge (stdin/file)
|
||||
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
|
||||
let json = if let Some(path) = &self.config.json_file {
|
||||
@ -808,6 +816,145 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal AOT build pipeline driven by nyash.toml (MVP, single-platform, best-effort)
|
||||
fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> {
|
||||
use std::path::{Path, PathBuf};
|
||||
let cwd = std::env::current_dir().unwrap_or(PathBuf::from("."));
|
||||
let cfg_abspath = if Path::new(cfg_path).is_absolute() { PathBuf::from(cfg_path) } else { cwd.join(cfg_path) };
|
||||
// 1) Load nyash.toml
|
||||
let text = std::fs::read_to_string(&cfg_abspath).map_err(|e| format!("read {}: {}", cfg_abspath.display(), e))?;
|
||||
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_abspath.display(), e))?;
|
||||
// 2) Apply [env]
|
||||
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
||||
for (k, v) in env_tbl.iter() { if let Some(s) = v.as_str() { std::env::set_var(k, s); } }
|
||||
}
|
||||
// Derive options
|
||||
let profile = self.config.build_profile.clone().unwrap_or_else(|| "release".into());
|
||||
let aot = self.config.build_aot.clone().unwrap_or_else(|| "cranelift".into());
|
||||
let out = self.config.build_out.clone();
|
||||
let target = self.config.build_target.clone();
|
||||
// 3) Build plugins: read [plugins] values as paths and build each
|
||||
if let Some(pl_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
||||
for (name, v) in pl_tbl.iter() {
|
||||
if let Some(path) = v.as_str() {
|
||||
let p = if Path::new(path).is_absolute() { PathBuf::from(path) } else { cwd.join(path) };
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
if profile == "release" { cmd.arg("--release"); }
|
||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||
cmd.current_dir(&p);
|
||||
println!("[build] plugin {} at {}", name, p.display());
|
||||
let status = cmd.status().map_err(|e| format!("spawn cargo (plugin {}): {}", name, e))?;
|
||||
if !status.success() {
|
||||
return Err(format!("plugin build failed: {} (dir={})", name, p.display()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4) Build nyash core (features)
|
||||
{
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
if profile == "release" { cmd.arg("--release"); }
|
||||
match aot.as_str() { "llvm" => { cmd.args(["--features","llvm"]); }, _ => { cmd.args(["--features","cranelift-jit"]); } }
|
||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||
println!("[build] nyash core ({}, features={})", profile, if aot=="llvm" {"llvm"} else {"cranelift-jit"});
|
||||
let status = cmd.status().map_err(|e| format!("spawn cargo (core): {}", e))?;
|
||||
if !status.success() { return Err("nyash core build failed".into()); }
|
||||
}
|
||||
// 5) Determine app entry
|
||||
let app = if let Some(a) = self.config.build_app.clone() { a } else {
|
||||
// try [build].app, else suggest
|
||||
if let Some(tbl) = doc.get("build").and_then(|v| v.as_table()) {
|
||||
if let Some(s) = tbl.get("app").and_then(|v| v.as_str()) { s.to_string() } else { String::new() }
|
||||
} else { String::new() }
|
||||
};
|
||||
let app = if !app.is_empty() { app } else {
|
||||
// collect candidates under apps/**/main.nyash
|
||||
let mut cand: Vec<String> = Vec::new();
|
||||
fn walk(dir: &Path, acc: &mut Vec<String>) {
|
||||
if let Ok(rd) = std::fs::read_dir(dir) {
|
||||
for e in rd.flatten() {
|
||||
let p = e.path();
|
||||
if p.is_dir() { walk(&p, acc); }
|
||||
else if p.file_name().map(|n| n=="main.nyash").unwrap_or(false) {
|
||||
acc.push(p.display().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(&cwd.join("apps"), &mut cand);
|
||||
let msg = if cand.is_empty() {
|
||||
"no app specified (--app) and no apps/**/main.nyash found".to_string()
|
||||
} else {
|
||||
format!("no app specified (--app). Candidates:\n - {}", cand.join("\n - "))
|
||||
};
|
||||
return Err(msg);
|
||||
};
|
||||
// 6) Emit object
|
||||
let obj_dir = cwd.join("target").join("aot_objects");
|
||||
let _ = std::fs::create_dir_all(&obj_dir);
|
||||
let obj_path = obj_dir.join("main.o");
|
||||
if aot == "llvm" {
|
||||
if std::env::var("LLVM_SYS_180_PREFIX").ok().is_none() && std::env::var("LLVM_SYS_181_PREFIX").ok().is_none() {
|
||||
return Err("LLVM 18 not configured. Set LLVM_SYS_180_PREFIX or install LLVM 18 (llvm-config)".into());
|
||||
}
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
||||
println!("[emit] LLVM object → {}", obj_path.display());
|
||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||
.args(["--backend","llvm", &app])
|
||||
.status().map_err(|e| format!("spawn nyash llvm: {}", e))?;
|
||||
if !status.success() { return Err("LLVM emit failed".into()); }
|
||||
} else {
|
||||
std::env::set_var("NYASH_AOT_OBJECT_OUT", &obj_dir);
|
||||
println!("[emit] Cranelift object → {} (directory)", obj_dir.display());
|
||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||
.args(["--backend","vm", &app])
|
||||
.status().map_err(|e| format!("spawn nyash jit-aot: {}", e))?;
|
||||
if !status.success() { return Err("Cranelift emit failed".into()); }
|
||||
}
|
||||
if !obj_path.exists() {
|
||||
// In Cranelift path we produce target/aot_objects/<name>.o; fall back to main.o default
|
||||
if !obj_dir.join("main.o").exists() { return Err(format!("object not generated under {}", obj_dir.display())); }
|
||||
}
|
||||
let out_path = if let Some(o) = out { PathBuf::from(o) } else { if cfg!(windows) { cwd.join("app.exe") } else { cwd.join("app") } };
|
||||
// 7) Link
|
||||
println!("[link] → {}", out_path.display());
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Prefer MSVC link.exe, then clang fallback
|
||||
if let Ok(link) = which::which("link") {
|
||||
let status = std::process::Command::new(&link).args(["/NOLOGO", &format!("/OUT:{}", out_path.display().to_string())])
|
||||
.arg(&obj_path)
|
||||
.arg(cwd.join("target").join("release").join("nyrt.lib"))
|
||||
.status().map_err(|e| format!("spawn link.exe: {}", e))?;
|
||||
if status.success() { println!("OK"); return Ok(()); }
|
||||
}
|
||||
if let Ok(clang) = which::which("clang") {
|
||||
let status = std::process::Command::new(&clang)
|
||||
.args(["-o", &out_path.display().to_string(), &obj_path.display().to_string()])
|
||||
.arg(cwd.join("target").join("release").join("nyrt.lib").display().to_string())
|
||||
.arg("-lntdll")
|
||||
.status().map_err(|e| format!("spawn clang: {}", e))?;
|
||||
if status.success() { println!("OK"); return Ok(()); }
|
||||
return Err("link failed on Windows (tried link.exe and clang)".into());
|
||||
}
|
||||
return Err("no linker found (need Visual Studio link.exe or LLVM clang)".into());
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let status = std::process::Command::new("cc")
|
||||
.arg(&obj_path)
|
||||
.args(["-L", &cwd.join("target").join("release").display().to_string()])
|
||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive", "-lpthread", "-ldl", "-lm"])
|
||||
.args(["-o", &out_path.display().to_string()])
|
||||
.status().map_err(|e| format!("spawn cc: {}", e))?;
|
||||
if !status.success() { return Err("link failed (cc)".into()); }
|
||||
}
|
||||
println!("✅ Success: {}", out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
|
||||
Reference in New Issue
Block a user