Skip to main content

cargo_macra/
lib.rs

1use std::path::{Path, PathBuf};
2
3pub mod parse_normal;
4pub mod parse_trace;
5pub mod trace_macros;
6
7/// Normalize token-like text for resilient comparisons.
8///
9/// - Removes spaces adjacent to punctuation (e.g., `a :: b` -> `a::b`)
10/// - Collapses remaining whitespace to a single space
11/// - Normalizes bracket types (`{}`, `[]` -> `()`)
12pub fn normalize_tokens(s: &str) -> String {
13    let chars: Vec<char> = s.chars().collect();
14    let mut result = String::with_capacity(chars.len());
15
16    fn is_punct(c: char) -> bool {
17        !c.is_alphanumeric() && c != '_' && c != '"' && c != '\'' && !c.is_whitespace()
18    }
19
20    let mut i = 0;
21    while i < chars.len() {
22        let c = chars[i];
23        if c.is_whitespace() {
24            let prev = result.chars().last();
25            while i < chars.len() && chars[i].is_whitespace() {
26                i += 1;
27            }
28            let next = chars.get(i).copied();
29            let prev_is_punct = prev.is_none_or(is_punct);
30            let next_is_punct = next.is_none_or(is_punct);
31            if !prev_is_punct && !next_is_punct {
32                result.push(' ');
33            }
34        } else {
35            match c {
36                '{' | '[' => result.push('('),
37                '}' | ']' => result.push(')'),
38                _ => result.push(c),
39            }
40            i += 1;
41        }
42    }
43
44    result
45}
46
47#[cfg(target_os = "macos")]
48const HOOK_LIB_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libmacra_hook.dylib"));
49
50#[cfg(target_os = "windows")]
51const HOOK_LIB_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/macra_hook.dll"));
52
53#[cfg(not(any(target_os = "macos", target_os = "windows")))]
54const HOOK_LIB_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libmacra_hook.so"));
55
56#[cfg(target_os = "windows")]
57const WRAPPER_EXE_BYTES: &[u8] =
58    include_bytes!(concat!(env!("OUT_DIR"), "/macra-rustc-wrapper.exe"));
59
60/// Extract the embedded hook library to `~/.cache/cargo-macra/` if needed,
61/// returning the path to the cached file.
62fn ensure_hook_lib() -> Option<PathBuf> {
63    let lib_name = if cfg!(target_os = "macos") {
64        "libmacra_hook.dylib"
65    } else if cfg!(target_os = "windows") {
66        "macra_hook.dll"
67    } else {
68        "libmacra_hook.so"
69    };
70    let arch = std::env::consts::ARCH;
71
72    let version = env!("CARGO_PKG_VERSION");
73    let file_name = if cfg!(target_os = "windows") {
74        format!("macra_hook-{}-{}.dll", version, arch)
75    } else if cfg!(target_os = "macos") {
76        format!("libmacra_hook-{}-{}.dylib", version, arch)
77    } else {
78        format!("libmacra_hook-{}-{}.so", version, arch)
79    };
80
81    let cache_dir = dirs_cache()?;
82    let dest = cache_dir.join(&file_name);
83    let dest_plain = if cfg!(target_os = "windows") {
84        cache_dir.join(format!("macra_hook-{}.dll", arch))
85    } else if cfg!(target_os = "macos") {
86        cache_dir.join(format!("libmacra_hook-{}.dylib", arch))
87    } else {
88        cache_dir.join(format!("libmacra_hook-{}.so", arch))
89    };
90
91    // If cached file exists with the right size, reuse it
92    if let Ok(meta) = std::fs::metadata(&dest) {
93        if meta.len() == HOOK_LIB_BYTES.len() as u64 {
94            // Ensure the arch-specific alias copy exists too.
95            if !dest_plain.exists() {
96                let _ = std::fs::copy(&dest, &dest_plain);
97            }
98            // Keep backward compatibility with the legacy plain file name.
99            let _ = std::fs::copy(&dest, cache_dir.join(lib_name));
100            return Some(dest_plain);
101        }
102    }
103
104    // Write the embedded bytes
105    if std::fs::create_dir_all(&cache_dir).is_err() {
106        return None;
107    }
108    if std::fs::write(&dest, HOOK_LIB_BYTES).is_err() {
109        return None;
110    }
111    let _ = std::fs::copy(&dest, &dest_plain);
112    let _ = std::fs::copy(&dest, cache_dir.join(lib_name));
113
114    Some(dest_plain)
115}
116
117/// Extract the embedded RUSTC_WRAPPER executable to the cache directory (Windows only).
118#[cfg(target_os = "windows")]
119fn ensure_wrapper_exe() -> Option<PathBuf> {
120    let version = env!("CARGO_PKG_VERSION");
121    let arch = std::env::consts::ARCH;
122    let file_name = format!("macra-rustc-wrapper-{}-{}.exe", version, arch);
123
124    let cache_dir = dirs_cache()?;
125    let dest = cache_dir.join(&file_name);
126    let dest_plain = cache_dir.join(format!("macra-rustc-wrapper-{}.exe", arch));
127
128    if let Ok(meta) = std::fs::metadata(&dest) {
129        if meta.len() == WRAPPER_EXE_BYTES.len() as u64 {
130            if !dest_plain.exists() {
131                let _ = std::fs::copy(&dest, &dest_plain);
132            }
133            return Some(dest_plain);
134        }
135    }
136
137    if std::fs::create_dir_all(&cache_dir).is_err() {
138        return None;
139    }
140    if std::fs::write(&dest, WRAPPER_EXE_BYTES).is_err() {
141        return None;
142    }
143    let _ = std::fs::copy(&dest, &dest_plain);
144
145    Some(dest_plain)
146}
147
148/// Find the macra-rustc-wrapper executable (Windows only).
149#[cfg(target_os = "windows")]
150pub fn find_wrapper_exe(current_exe: Option<&Path>) -> Option<PathBuf> {
151    let exe_name = "macra-rustc-wrapper.exe";
152    let arch = std::env::consts::ARCH;
153    let arch_name = format!("macra-rustc-wrapper-{}.exe", arch);
154
155    if let Some(exe) = current_exe {
156        if let Some(dir) = exe.parent() {
157            let wrapper = dir.join(exe_name);
158            if wrapper.exists() {
159                return Some(wrapper);
160            }
161        }
162    }
163
164    let paths = [
165        PathBuf::from(format!("./target/debug/{}", arch_name)),
166        PathBuf::from(format!("./target/release/{}", arch_name)),
167        PathBuf::from(format!("./target/debug/{}", exe_name)),
168        PathBuf::from(format!("./target/release/{}", exe_name)),
169    ];
170
171    for path in paths {
172        if path.exists() {
173            return Some(path);
174        }
175    }
176
177    ensure_wrapper_exe()
178}
179
180/// Return `~/.cache/cargo-macra/` (or platform equivalent).
181fn dirs_cache() -> Option<PathBuf> {
182    #[cfg(target_os = "windows")]
183    {
184        std::env::var_os("LOCALAPPDATA").map(|d| PathBuf::from(d).join("cargo-macra"))
185    }
186    #[cfg(not(target_os = "windows"))]
187    {
188        std::env::var_os("XDG_CACHE_HOME")
189            .map(PathBuf::from)
190            .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cache")))
191            .map(|d| d.join("cargo-macra"))
192    }
193}
194
195/// Find the macra-hook shared library.
196///
197/// `current_exe` should be the current executable path when available.
198pub fn find_hook_lib(current_exe: Option<&Path>) -> Option<PathBuf> {
199    let lib_name = if cfg!(target_os = "macos") {
200        "libmacra_hook.dylib"
201    } else if cfg!(target_os = "windows") {
202        "macra_hook.dll"
203    } else {
204        "libmacra_hook.so"
205    };
206    let arch = std::env::consts::ARCH;
207    let arch_name = if cfg!(target_os = "windows") {
208        format!("macra_hook-{}.dll", arch)
209    } else if cfg!(target_os = "macos") {
210        format!("libmacra_hook-{}.dylib", arch)
211    } else {
212        format!("libmacra_hook-{}.so", arch)
213    };
214
215    if let Some(exe) = current_exe {
216        if let Some(dir) = exe.parent() {
217            let hook_lib = dir.join(lib_name);
218            if hook_lib.exists() {
219                return Some(hook_lib);
220            }
221        }
222    }
223
224    let paths = [
225        PathBuf::from(format!("./target/debug/{}", arch_name)),
226        PathBuf::from(format!("./target/release/{}", arch_name)),
227        PathBuf::from(format!("./target/debug/{}", lib_name)),
228        PathBuf::from(format!("./target/release/{}", lib_name)),
229    ];
230
231    for path in paths {
232        if path.exists() {
233            return Some(path);
234        }
235    }
236
237    // Fallback: extract embedded library to cache
238    ensure_hook_lib()
239}