use std::{
collections::BTreeMap,
env, fs,
path::PathBuf,
sync::LazyLock,
time::{Duration, Instant},
};
use serde::Deserialize;
const FILE_NAME: &str = "ezffi-typemap.toml";
static MAP_PATH: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
let manifest = env::var("CARGO_MANIFEST_DIR").ok()?;
let mut dir = PathBuf::from(manifest);
let workspace_root = loop {
if dir.join("Cargo.lock").exists() {
break dir;
}
if !dir.pop() {
return None;
}
};
Some(workspace_root.join("target").join(FILE_NAME))
});
#[derive(Deserialize)]
pub struct TypeEntry {
pub c_type: String,
pub c_compatible: bool,
}
#[derive(Deserialize, Default)]
pub struct Section {
pub codegen_version: u32,
#[serde(flatten)]
pub types: BTreeMap<String, TypeEntry>,
}
pub type Sections = BTreeMap<String, Section>;
pub struct TypeMap {
sections: Sections,
_lock: Lock,
}
impl TypeMap {
pub fn load() -> Option<TypeMap> {
MAP_PATH.as_ref()?;
Some(TypeMap {
sections: read(),
_lock: Lock::acquire(),
})
}
pub fn sections(&self) -> &Sections {
&self.sections
}
pub fn store(mut self, krate: String, section: Section) {
self.sections.insert(krate, section);
write(&self.sections);
}
}
fn read() -> Sections {
let Some(path) = &*MAP_PATH else {
return Sections::new();
};
let Ok(content) = fs::read_to_string(path) else {
return Sections::new();
};
toml::from_str(&content).unwrap_or_default()
}
fn write(sections: &Sections) {
let Some(path) = &*MAP_PATH else {
return;
};
let mut out =
String::from("# Generated by ezffi. Maps each crate's Rust types to their FFI types.\n\n");
for (krate, section) in sections {
out.push_str(&format!("[{krate}]\n"));
out.push_str(&format!("codegen_version = {}\n", section.codegen_version));
for (rust_ty, entry) in §ion.types {
out.push_str(&format!(
"{rust_ty} = {{ c_type = {:?}, c_compatible = {} }}\n",
entry.c_type, entry.c_compatible,
));
}
out.push('\n');
}
let _ = fs::write(path, out);
}
struct Lock(Option<PathBuf>);
impl Lock {
fn acquire() -> Lock {
let Some(map_path) = &*MAP_PATH else {
return Lock(None);
};
let lock_path = map_path.with_extension("lock");
if let Some(parent) = lock_path.parent() {
let _ = fs::create_dir_all(parent);
}
let start = Instant::now();
loop {
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&lock_path)
{
Ok(_) => return Lock(Some(lock_path)),
Err(_) if start.elapsed() > Duration::from_secs(10) => {
let _ = fs::remove_file(&lock_path);
}
Err(_) => std::thread::sleep(Duration::from_millis(15)),
}
}
}
}
impl Drop for Lock {
fn drop(&mut self) {
if let Some(path) = &self.0 {
let _ = fs::remove_file(path);
}
}
}