autozig_engine/
zig_compiler.rs

1//! Zig compiler wrapper with target support
2
3use std::{
4    path::Path,
5    process::Command,
6};
7
8use anyhow::{
9    Context,
10    Result,
11};
12
13/// Wrapper for invoking the Zig compiler
14pub struct ZigCompiler {
15    zig_path: String,
16}
17
18impl ZigCompiler {
19    /// Create a new Zig compiler wrapper
20    pub fn new() -> Self {
21        // Check for ZIG_PATH environment variable, otherwise use "zig"
22        let zig_path = std::env::var("ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
23        Self { zig_path }
24    }
25
26    /// Check Zig compiler version
27    pub fn check_version(&self) -> Result<String> {
28        let output = Command::new(&self.zig_path)
29            .arg("version")
30            .output()
31            .context("Failed to execute zig version command")?;
32
33        if !output.status.success() {
34            anyhow::bail!("Zig compiler not found or failed to run");
35        }
36
37        let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
38        Ok(version)
39    }
40
41    /// Compile Zig source to static library with target support
42    ///
43    /// # Arguments
44    /// * `source` - Path to .zig source file
45    /// * `output_lib` - Path for output static library (.a)
46    /// * `target` - Target triple (e.g., "x86_64-linux-gnu", "native")
47    pub fn compile_with_target(
48        &self,
49        source: &Path,
50        output_lib: &Path,
51        target: &str,
52    ) -> Result<()> {
53        println!("cargo:warning=Compiling Zig code: {} for target: {}", source.display(), target);
54
55        // zig build-lib source.zig -static -femit-bin=output.a -target <target> -fPIC
56        // -lc NOTE: We removed -femit-h because it's experimental and unstable
57        // FFI bindings will be generated directly from Rust signatures (IDL-driven)
58        // -fPIC is required for linking with PIE executables (Rust default)
59        // -lc is required for linking with libc (needed for c_allocator)
60        let status = Command::new(&self.zig_path)
61            .arg("build-lib")
62            .arg(source)
63            .arg("-static")
64            .arg(format!("-femit-bin={}", output_lib.display()))
65            .arg("-target")
66            .arg(target)
67            // Generate Position Independent Code (required for PIE executables)
68            .arg("-fPIC")
69            // Link with libc (required for c_allocator and other libc functions)
70            .arg("-lc")
71            // Optimize for release builds
72            .arg("-O")
73            .arg("ReleaseFast")
74            .status()
75            .context("Failed to execute zig build-lib")?;
76
77        if !status.success() {
78            anyhow::bail!("Zig compilation failed");
79        }
80
81        println!("cargo:warning=Zig compilation successful");
82        println!("cargo:warning=Library: {}", output_lib.display());
83
84        Ok(())
85    }
86
87    /// Compile with native target (convenience method)
88    pub fn compile(&self, source: &Path, output_lib: &Path) -> Result<()> {
89        self.compile_with_target(source, output_lib, "native")
90    }
91
92    /// Compile Zig tests to an executable
93    ///
94    /// # Arguments
95    /// * `source` - Path to .zig source file containing tests
96    /// * `output_exe` - Path for output test executable
97    /// * `target` - Target triple (e.g., "x86_64-linux-gnu", "native")
98    pub fn compile_tests(&self, source: &Path, output_exe: &Path, target: &str) -> Result<()> {
99        println!("cargo:warning=Compiling Zig tests: {} for target: {}", source.display(), target);
100
101        // zig test source.zig -femit-bin=output_exe -target <target>
102        let status = Command::new(&self.zig_path)
103            .arg("test")
104            .arg(source)
105            .arg(format!("-femit-bin={}", output_exe.display()))
106            .arg("-target")
107            .arg(target)
108            // Optimize for release builds
109            .arg("-O")
110            .arg("ReleaseFast")
111            .status()
112            .context("Failed to execute zig test")?;
113
114        if !status.success() {
115            anyhow::bail!("Zig test compilation failed");
116        }
117
118        println!("cargo:warning=Zig test compilation successful");
119        println!("cargo:warning=Test executable: {}", output_exe.display());
120
121        Ok(())
122    }
123
124    /// Run compiled Zig test executable
125    ///
126    /// # Arguments
127    /// * `test_exe` - Path to compiled test executable
128    pub fn run_test_executable(&self, test_exe: &Path) -> Result<String> {
129        let output = Command::new(test_exe)
130            .output()
131            .context(format!("Failed to execute test: {}", test_exe.display()))?;
132
133        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
134        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
135
136        if !output.status.success() {
137            anyhow::bail!("Zig tests failed:\nStdout: {}\nStderr: {}", stdout, stderr);
138        }
139
140        Ok(format!("Stdout: {}\nStderr: {}", stdout, stderr))
141    }
142}
143
144impl Default for ZigCompiler {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_compiler_creation() {
156        let compiler = ZigCompiler::new();
157        assert!(!compiler.zig_path.is_empty());
158    }
159
160    #[test]
161    #[ignore] // Only run if Zig is installed
162    fn test_check_version() {
163        let compiler = ZigCompiler::new();
164        let version = compiler.check_version();
165        if version.is_ok() {
166            println!("Zig version: {}", version.unwrap());
167        }
168    }
169}