fix: embed C compiler path at build time for self-contained binary

build.rs captures the absolute path to cc/gcc/clang during compilation
and bakes it into the binary. On Nix systems this embeds the full
/nix/store path so `lux compile` works without cc on PATH.

Lookup order: $CC env var > embedded build-time path > PATH search.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 08:12:18 -05:00
parent dbdd3cca57
commit 8e788c8a9f
2 changed files with 70 additions and 2 deletions

38
build.rs Normal file
View File

@@ -0,0 +1,38 @@
use std::path::PathBuf;
fn main() {
// Capture the absolute C compiler path at build time so the binary is self-contained.
// This is critical for Nix builds where cc/gcc live in /nix/store paths.
let cc_path = std::env::var("CC").ok()
.filter(|s| !s.is_empty())
.and_then(|s| resolve_absolute(&s))
.or_else(|| find_in_path("cc"))
.or_else(|| find_in_path("gcc"))
.or_else(|| find_in_path("clang"))
.unwrap_or_default();
println!("cargo:rustc-env=LUX_CC_PATH={}", cc_path);
println!("cargo:rerun-if-env-changed=CC");
println!("cargo:rerun-if-env-changed=PATH");
}
/// Resolve a command name to its absolute path by searching PATH.
fn find_in_path(cmd: &str) -> Option<String> {
let path_var = std::env::var("PATH").ok()?;
for dir in path_var.split(':') {
let candidate = PathBuf::from(dir).join(cmd);
if candidate.is_file() {
return Some(candidate.to_string_lossy().into_owned());
}
}
None
}
/// If the path is already absolute and exists, return it. Otherwise search PATH.
fn resolve_absolute(cmd: &str) -> Option<String> {
let p = PathBuf::from(cmd);
if p.is_absolute() && p.is_file() {
return Some(cmd.to_string());
}
find_in_path(cmd)
}

View File

@@ -726,6 +726,36 @@ fn collect_lux_files_nonrecursive(dir: &str, pattern: Option<&str>, files: &mut
}
}
/// Find a C compiler. Priority: $CC env var, build-time embedded path, PATH search.
fn find_c_compiler() -> String {
// 1. Explicit env var
if let Ok(cc) = std::env::var("CC") {
if !cc.is_empty() {
return cc;
}
}
// 2. Path captured at build time (e.g. absolute nix store path)
let built_in = env!("LUX_CC_PATH");
if !built_in.is_empty() && std::path::Path::new(built_in).exists() {
return built_in.to_string();
}
// 3. Search PATH
for name in &["cc", "gcc", "clang"] {
if let Ok(output) = std::process::Command::new("which").arg(name).output() {
if output.status.success() {
if let Ok(p) = String::from_utf8(output.stdout) {
let p = p.trim();
if !p.is_empty() {
return p.to_string();
}
}
}
}
}
// 4. Last resort
"cc".to_string()
}
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c: bool) {
use codegen::c_backend::CBackend;
use modules::ModuleLoader;
@@ -817,8 +847,8 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c:
std::process::exit(1);
}
// Find C compiler
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
// Find C compiler: $CC env var > embedded build-time path > PATH search
let cc = find_c_compiler();
let compile_result = Command::new(&cc)
.args(["-O2", "-o"])