From 8e788c8a9f94e0d39f38c4f5452039b5bd9d1db6 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Wed, 18 Feb 2026 08:12:18 -0500 Subject: [PATCH] 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 --- build.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 build.rs diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f5cccf3 --- /dev/null +++ b/build.rs @@ -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 { + 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 { + let p = PathBuf::from(cmd); + if p.is_absolute() && p.is_file() { + return Some(cmd.to_string()); + } + find_in_path(cmd) +} diff --git a/src/main.rs b/src/main.rs index 7d81d62..23fb476 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"])