Files
hakorune/src/runner/build.rs
Selfhosting Dev b573c3e5b8 feat: LLVM_SYS_180_PREFIX環境変数削除完了!llvm-harness経路でRust LLVMバインディング不要化達成
🚀 Phase 15.5 MIR Call統一革命 - LLVM環境変数削除フェーズ完了

##  完了内容
- **条件分岐実装**: llvm-harness(デフォルト)はLLVM_SYS_180_PREFIX不要
- **後方互換性維持**: llvm-inkwell-legacy使用時はLLVM_SYS_180_PREFIX必要
- **全ツール統一**: 12個のビルドスクリプト・テストスクリプトを一括更新
- **ドキュメント更新**: ENV_VARS.mdでLLVM feature選択方法を明記

## 🛠️ 更新ファイル
- **コアビルド**: src/runner/build.rs, tools/build_llvm.sh, build_llvm.sh
- **スモークテスト**: tools/llvm_smoke.sh, tools/test/smoke/llvm/ir_phi_empty_check.sh
- **CI設定**: .github/workflows/min-gate.yml
- **Windows版**: build_llvm_wsl.sh, build_llvm_wsl_msvc.sh (cross-compilation)
- **開発ツール**: tools/build_compiler_exe.sh, tools/ny_mir_builder.sh
- **ドキュメント**: docs/development/runtime/ENV_VARS.md

##  技術的成果
- **環境変数削減**: LLVM_SYS_180_PREFIX → 条件付き使用のみ
- **Python LLVM統合**: llvmliteハーネス経路でRust LLVM依存完全除去
- **ビルド簡略化**: デフォルトでllvm-config-18のみ必要
- **動作確認**: tools/llvm_smoke.sh成功 (1648バイト.oファイル生成)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-24 03:28:24 +09:00

269 lines
9.0 KiB
Rust

use super::NyashRunner;
use std::path::{Path, PathBuf};
pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result<(), String> {
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 = runner
.config
.build_profile
.clone()
.unwrap_or_else(|| "release".into());
let aot = runner
.config
.build_aot
.clone()
.unwrap_or_else(|| "cranelift".into());
let out = runner.config.build_out.clone();
let target = runner.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) = runner.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" {
// llvmliteハーネス使用によりLLVM_SYS_180_PREFIX不要
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(())
}