autozig_engine/
zig_compiler.rs1use std::{
4 path::Path,
5 process::Command,
6};
7
8use anyhow::{
9 Context,
10 Result,
11};
12
13pub struct ZigCompiler {
15 zig_path: String,
16}
17
18impl ZigCompiler {
19 pub fn new() -> Self {
21 let zig_path = std::env::var("ZIG_PATH").unwrap_or_else(|_| "zig".to_string());
23 Self { zig_path }
24 }
25
26 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 pub fn compile_with_target(
49 &self,
50 source: &Path,
51 output_lib: &Path,
52 target: &str,
53 ) -> Result<()> {
54 println!("cargo:warning=Compiling Zig code: {} for target: {}", source.display(), target);
55
56 let c_sources = self.find_c_sources(source)?;
58
59 if !c_sources.is_empty() {
60 println!(
61 "cargo:warning=Found {} C source file(s) to compile with Zig",
62 c_sources.len()
63 );
64 for c_file in &c_sources {
65 println!("cargo:warning= - {}", c_file.display());
66 }
67 }
68
69 let is_wasm = target.contains("wasm32");
71
72 let mut cmd = Command::new(&self.zig_path);
74 cmd.arg("build-lib")
75 .arg(source)
76 .arg("-static")
77 .arg(format!("-femit-bin={}", output_lib.display()))
78 .arg("-target")
79 .arg(target);
80
81 if is_wasm {
82 println!("cargo:warning=Detected WASM target, applying WASM-specific flags");
84
85 cmd.arg("-fno-stack-protector");
87
88 cmd.arg("-mcpu=mvp+simd128");
91
92 cmd.arg("-O").arg("ReleaseFast");
95
96 } else {
99 cmd.arg("-fPIC");
102
103 cmd.arg("-lc");
105
106 cmd.arg("-O").arg("ReleaseFast");
108 }
109
110 for c_file in &c_sources {
112 cmd.arg(c_file);
113 }
114
115 let status = cmd.status().context("Failed to execute zig build-lib")?;
116
117 if !status.success() {
118 anyhow::bail!("Zig compilation failed");
119 }
120
121 println!("cargo:warning=Zig compilation successful");
122 println!("cargo:warning=Library: {}", output_lib.display());
123
124 Ok(())
125 }
126
127 pub fn compile_with_target_and_src(
137 &self,
138 source: &Path,
139 output_lib: &Path,
140 target: &str,
141 src_dir: &Path,
142 ) -> Result<()> {
143 println!("cargo:warning=Compiling Zig code: {} for target: {}", source.display(), target);
144
145 let c_sources = self.find_c_sources_in_dir(src_dir)?;
147
148 if !c_sources.is_empty() {
149 println!(
150 "cargo:warning=Found {} C source file(s) to compile with Zig",
151 c_sources.len()
152 );
153 for c_file in &c_sources {
154 println!("cargo:warning= - {}", c_file.display());
155 }
156 }
157
158 let is_wasm = target.contains("wasm32");
160
161 let mut cmd = Command::new(&self.zig_path);
162 cmd.arg("build-lib")
163 .arg(source)
164 .arg("-static")
165 .arg(format!("-femit-bin={}", output_lib.display()))
166 .arg("-target")
167 .arg(target);
168
169 if is_wasm {
170 cmd.arg("-fno-stack-protector")
172 .arg("-mcpu=mvp+simd128")
174 .arg("-O")
175 .arg("ReleaseFast");
176 } else {
177 cmd.arg("-fPIC").arg("-lc").arg("-O").arg("ReleaseFast");
179 }
180
181 for c_file in &c_sources {
183 cmd.arg(c_file);
184 }
185
186 let status = cmd.status().context("Failed to execute zig build-lib")?;
187
188 if !status.success() {
189 anyhow::bail!("Zig compilation failed");
190 }
191
192 println!("cargo:warning=Zig compilation successful");
193 println!("cargo:warning=Library: {}", output_lib.display());
194
195 Ok(())
196 }
197
198 fn find_c_sources_in_dir(&self, dir: &Path) -> Result<Vec<std::path::PathBuf>> {
200 let mut c_sources = Vec::new();
201
202 if dir.exists() {
203 for entry in std::fs::read_dir(dir)? {
204 let entry = entry?;
205 let path = entry.path();
206
207 if path.is_file() {
208 if let Some(ext) = path.extension() {
209 if ext == "c" {
210 c_sources.push(path);
211 }
212 }
213 }
214 }
215 }
216
217 Ok(c_sources)
218 }
219
220 fn find_c_sources(&self, zig_source: &Path) -> Result<Vec<std::path::PathBuf>> {
222 let mut c_sources = Vec::new();
223
224 if let Some(parent) = zig_source.parent() {
225 if parent.exists() {
226 for entry in std::fs::read_dir(parent)? {
227 let entry = entry?;
228 let path = entry.path();
229
230 if path.is_file() {
231 if let Some(ext) = path.extension() {
232 if ext == "c" {
233 c_sources.push(path);
234 }
235 }
236 }
237 }
238 }
239 }
240
241 Ok(c_sources)
242 }
243
244 pub fn compile(&self, source: &Path, output_lib: &Path) -> Result<()> {
246 self.compile_with_target(source, output_lib, "native")
247 }
248
249 pub fn compile_tests(&self, source: &Path, output_exe: &Path, target: &str) -> Result<()> {
256 println!("cargo:warning=Compiling Zig tests: {} for target: {}", source.display(), target);
257
258 let status = Command::new(&self.zig_path)
260 .arg("test")
261 .arg(source)
262 .arg(format!("-femit-bin={}", output_exe.display()))
263 .arg("-target")
264 .arg(target)
265 .arg("-O")
267 .arg("ReleaseFast")
268 .status()
269 .context("Failed to execute zig test")?;
270
271 if !status.success() {
272 anyhow::bail!("Zig test compilation failed");
273 }
274
275 println!("cargo:warning=Zig test compilation successful");
276 println!("cargo:warning=Test executable: {}", output_exe.display());
277
278 Ok(())
279 }
280
281 pub fn run_test_executable(&self, test_exe: &Path) -> Result<String> {
286 let output = Command::new(test_exe)
287 .output()
288 .context(format!("Failed to execute test: {}", test_exe.display()))?;
289
290 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
291 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
292
293 if !output.status.success() {
294 anyhow::bail!("Zig tests failed:\nStdout: {}\nStderr: {}", stdout, stderr);
295 }
296
297 Ok(format!("Stdout: {}\nStderr: {}", stdout, stderr))
298 }
299}
300
301impl Default for ZigCompiler {
302 fn default() -> Self {
303 Self::new()
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[test]
312 fn test_compiler_creation() {
313 let compiler = ZigCompiler::new();
314 assert!(!compiler.zig_path.is_empty());
315 }
316
317 #[test]
318 #[ignore] fn test_check_version() {
320 let compiler = ZigCompiler::new();
321 let version = compiler.check_version();
322 if version.is_ok() {
323 println!("Zig version: {}", version.unwrap());
324 }
325 }
326}