1#![forbid(unsafe_code)]
10
11use std::{
12 env,
13 fs,
14 path::{
15 Path,
16 PathBuf,
17 },
18};
19
20use anyhow::{
21 Context,
22 Result,
23};
24use sha2::{
25 Digest,
26 Sha256,
27};
28
29pub mod scanner;
30pub mod type_mapper;
31pub mod zig_compiler;
32
33pub use scanner::ZigCodeScanner;
34pub use zig_compiler::ZigCompiler;
35
36pub struct AutoZigEngine {
38 out_dir: PathBuf,
40 src_dir: PathBuf,
42}
43
44impl AutoZigEngine {
45 pub fn new(src_dir: impl AsRef<Path>, out_dir: impl AsRef<Path>) -> Self {
47 Self {
48 src_dir: src_dir.as_ref().to_path_buf(),
49 out_dir: out_dir.as_ref().to_path_buf(),
50 }
51 }
52
53 pub fn build(&self) -> Result<BuildOutput> {
55 println!("cargo:rerun-if-changed={}", self.src_dir.display());
57
58 let scanner = ZigCodeScanner::new(&self.src_dir);
59 let zig_code = scanner.scan()?;
60
61 if zig_code.is_empty() {
62 return Ok(BuildOutput { lib_path: None });
64 }
65
66 let code_hash = format!("{:x}", Sha256::digest(&zig_code));
68 let hash_file = self.out_dir.join(".zig_code_hash");
69 let lib_path = self.out_dir.join("libautozig.a");
70
71 if hash_file.exists() && lib_path.exists() {
73 if let Ok(old_hash) = fs::read_to_string(&hash_file) {
74 if old_hash == code_hash {
75 println!("cargo:warning=Zig code unchanged, skipping compilation");
76 println!("cargo:rustc-link-search=native={}", self.out_dir.display());
77 println!("cargo:rustc-link-lib=static=autozig");
78 return Ok(BuildOutput { lib_path: Some(lib_path) });
79 }
80 }
81 }
82
83 let zig_file = self.out_dir.join("generated_autozig.zig");
85 fs::write(&zig_file, &zig_code).context("Failed to write Zig source file")?;
86
87 let rust_target = env::var("TARGET").unwrap_or_else(|_| "native".to_string());
89 let zig_target = rust_to_zig_target(&rust_target);
90
91 let compiler = ZigCompiler::new();
93 compiler.compile_with_target_and_src(&zig_file, &lib_path, zig_target, &self.src_dir)?;
94
95 fs::write(&hash_file, &code_hash).context("Failed to write hash file")?;
97
98 println!("cargo:rustc-link-search=native={}", self.out_dir.display());
100 println!("cargo:rustc-link-lib=static=autozig");
101
102 Ok(BuildOutput { lib_path: Some(lib_path) })
103 }
104}
105
106fn rust_to_zig_target(rust_target: &str) -> &str {
108 match rust_target {
109 "x86_64-unknown-linux-gnu" => "x86_64-linux-gnu",
111 "x86_64-unknown-linux-musl" => "x86_64-linux-musl",
112 "aarch64-unknown-linux-gnu" => "aarch64-linux-gnu",
113 "aarch64-unknown-linux-musl" => "aarch64-linux-musl",
114 "arm-unknown-linux-gnueabihf" => "arm-linux-gnueabihf",
115 "i686-unknown-linux-gnu" => "i386-linux-gnu",
116
117 "x86_64-apple-darwin" => "x86_64-macos",
119 "aarch64-apple-darwin" => "aarch64-macos",
120
121 "x86_64-pc-windows-msvc" => "x86_64-windows",
123 "x86_64-pc-windows-gnu" => "x86_64-windows-gnu",
124 "i686-pc-windows-msvc" => "i386-windows",
125 "aarch64-pc-windows-msvc" => "aarch64-windows",
126
127 "wasm32-unknown-unknown" => "wasm32-freestanding",
129 "wasm32-wasi" => "wasm32-wasi",
130
131 _ => "native",
133 }
134}
135
136#[derive(Debug)]
138pub struct BuildOutput {
139 pub lib_path: Option<PathBuf>,
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_engine_creation() {
149 let engine = AutoZigEngine::new("src", "target");
150 assert_eq!(engine.src_dir, PathBuf::from("src"));
151 assert_eq!(engine.out_dir, PathBuf::from("target"));
152 }
153
154 #[test]
155 fn test_target_mapping() {
156 assert_eq!(rust_to_zig_target("x86_64-unknown-linux-gnu"), "x86_64-linux-gnu");
157 assert_eq!(rust_to_zig_target("aarch64-apple-darwin"), "aarch64-macos");
158 assert_eq!(rust_to_zig_target("x86_64-pc-windows-msvc"), "x86_64-windows");
159 assert_eq!(rust_to_zig_target("wasm32-wasi"), "wasm32-wasi");
160 assert_eq!(rust_to_zig_target("unknown-target"), "native");
161 }
162}