autozig_engine/
lib.rs

1//! Core engine for AutoZig code generation
2//!
3//! This engine handles:
4//! 1. Scanning Rust source files for autozig! invocations
5//! 2. Extracting Zig code
6//! 3. Compiling Zig code to static libraries with incremental optimization
7//! 4. Target triple mapping for cross-compilation
8
9#![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
36/// Main engine for processing autozig! macros during build
37pub struct AutoZigEngine {
38    /// Output directory (usually OUT_DIR from build.rs)
39    out_dir: PathBuf,
40    /// Source directory to scan
41    src_dir: PathBuf,
42}
43
44impl AutoZigEngine {
45    /// Create a new AutoZig engine
46    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    /// Run the complete build pipeline with incremental compilation
54    pub fn build(&self) -> Result<BuildOutput> {
55        // Step 1: Scan source files for autozig! macros
56        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            // No Zig code found, nothing to do
63            return Ok(BuildOutput { lib_path: None });
64        }
65
66        // Step 2: Check if Zig code changed using hash
67        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        // Incremental compilation: skip if hash unchanged and lib exists
72        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        // Step 3: Write consolidated Zig code
84        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        // Step 4: Get target triple for cross-compilation
88        let rust_target = env::var("TARGET").unwrap_or_else(|_| "native".to_string());
89        let zig_target = rust_to_zig_target(&rust_target);
90
91        // Step 5: Compile Zig code (pass src_dir to find C sources)
92        let compiler = ZigCompiler::new();
93        compiler.compile_with_target_and_src(&zig_file, &lib_path, zig_target, &self.src_dir)?;
94
95        // Step 6: Save hash for incremental compilation
96        fs::write(&hash_file, &code_hash).context("Failed to write hash file")?;
97
98        // Step 7: Link the static library
99        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
106/// Map Rust target triple to Zig target
107fn rust_to_zig_target(rust_target: &str) -> &str {
108    match rust_target {
109        // Linux targets
110        "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        // macOS targets
118        "x86_64-apple-darwin" => "x86_64-macos",
119        "aarch64-apple-darwin" => "aarch64-macos",
120
121        // Windows targets
122        "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        // WebAssembly
128        "wasm32-unknown-unknown" => "wasm32-freestanding",
129        "wasm32-wasi" => "wasm32-wasi",
130
131        // Default to native
132        _ => "native",
133    }
134}
135
136/// Output from the build process
137#[derive(Debug)]
138pub struct BuildOutput {
139    /// Path to the generated static library
140    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}