// This file is auto-generated by alef. DO NOT EDIT.
use std::ffi::OsString;
use std::io;
use std::path::{Path, PathBuf};
const ALEF_EXPORT_GENERATED_HEADERS: &str = "ALEF_EXPORT_GENERATED_HEADERS";
fn feature_macro_suffix(feature: &str) -> String {
let mut name = feature.replace(|c: char| !c.is_ascii_alphanumeric(), "_");
name.make_ascii_uppercase();
name
}
fn export_generated_headers_requested() -> bool {
matches!(std::env::var(ALEF_EXPORT_GENERATED_HEADERS).as_deref(), Ok("1") | Ok("true"))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=src");
println!("cargo:rerun-if-changed=cbindgen.toml");
println!("cargo:rerun-if-env-changed={ALEF_EXPORT_GENERATED_HEADERS}");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
println!("cargo:rustc-link-arg-cdylib=-Wl,-install_name,@rpath/{{ lib_name }}.dylib");
println!("cargo:rustc-link-arg-cdylib=-Wl,-rpath,@loader_path");
}
if export_generated_headers_requested() {
export_generated_headers()?;
}
Ok(())
}
fn export_generated_headers() -> Result<(), Box<dyn std::error::Error>> {
let crate_dir = std::env::var("CARGO_MANIFEST_DIR")
.map_err(|error| io::Error::other(format!("read CARGO_MANIFEST_DIR for C header generation: {error}")))?;
let bindings = cbindgen::generate(&crate_dir)
.map_err(|error| io::Error::other(format!("generate C bindings for {crate_dir}: {error}")))?;
let mut bytes = Vec::new();
bindings.write(&mut bytes);
let mut header = String::from_utf8(bytes).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("cbindgen generated invalid UTF-8 for include/{{ header_name }}: {error}"),
)
})?;
{{ capsule_header_fixup }}
stamp_enabled_features(&mut header);
let destinations = [
Path::new("include/{{ header_name }}"),
{{ go_header_destination }} ];
publish_headers(&destinations, header.as_bytes())?;
Ok(())
}
fn stamp_enabled_features(header: &mut String) {
let marker = "/* Cargo features enabled in this build. */";
if header.contains(marker) || !header.contains("{{ prefix_upper }}_FEATURE_") {
return;
}
let mut defines: Vec<String> = std::env::vars_os()
.filter_map(|(key, _)| {
let feature = key.to_str()?.strip_prefix("CARGO_FEATURE_")?;
let name = feature_macro_suffix(feature);
Some(format!("#define {{ prefix_upper }}_FEATURE_{name} 1"))
})
.collect();
defines.sort();
defines.dedup();
let block = format!("\n{marker}\n{}\n", defines.join("\n"));
let anchor = "#define {{ prefix_upper }}_H\n";
match header.find(anchor) {
Some(at) => header.insert_str(at + anchor.len(), &block),
None => header.insert_str(0, &block),
}
}
fn publish_headers(destinations: &[&Path], contents: &[u8]) -> io::Result<()> {
let originals = destinations
.iter()
.map(|path| match std::fs::read(path) {
Ok(bytes) => Ok(Some(bytes)),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(io::Error::new(error.kind(), format!("read existing {}: {error}", path.display()))),
})
.collect::<io::Result<Vec<_>>>()?;
for (index, destination) in destinations.iter().enumerate() {
if let Err(error) = atomic_write(destination, contents) {
let rollback_errors = rollback_headers(&destinations[..=index], &originals[..=index]);
let suffix = if rollback_errors.is_empty() {
String::new()
} else {
format!("; rollback also failed: {}", rollback_errors.join("; "))
};
return Err(io::Error::new(
error.kind(),
format!("publish generated header to {}: {error}{suffix}", destination.display()),
));
}
}
Ok(())
}
fn rollback_headers(destinations: &[&Path], originals: &[Option<Vec<u8>>]) -> Vec<String> {
destinations
.iter()
.zip(originals)
.filter_map(|(destination, original)| {
let result = match original {
Some(bytes) => atomic_write(destination, bytes),
None => match std::fs::remove_file(destination) {
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
result => result,
},
};
result.err().map(|error| format!("restore {}: {error}", destination.display()))
})
.collect()
}
fn atomic_write(destination: &Path, contents: &[u8]) -> io::Result<()> {
let parent = destination.parent().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, format!("{} has no parent directory", destination.display()))
})?;
std::fs::create_dir_all(parent)
.map_err(|error| io::Error::new(error.kind(), format!("create {}: {error}", parent.display())))?;
let mut temporary_name: OsString = destination
.file_name()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "header destination has no file name"))?
.into();
temporary_name.push(format!(".alef-tmp-{}", std::process::id()));
let temporary: PathBuf = parent.join(temporary_name);
std::fs::write(&temporary, contents)
.map_err(|error| io::Error::new(error.kind(), format!("write {}: {error}", temporary.display())))?;
let mut backup_name: OsString = destination
.file_name()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "header destination has no file name"))?
.into();
backup_name.push(format!(".alef-backup-{}", std::process::id()));
let backup = parent.join(backup_name);
match std::fs::remove_file(&backup) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => {
let _ = std::fs::remove_file(&temporary);
return Err(io::Error::new(
error.kind(),
format!("remove stale {}: {error}", backup.display()),
));
}
}
let had_destination = destination.exists();
if had_destination {
std::fs::rename(destination, &backup).map_err(|error| {
let _ = std::fs::remove_file(&temporary);
io::Error::new(error.kind(), format!("stage existing {} for replacement: {error}", destination.display()))
})?;
}
if let Err(error) = std::fs::rename(&temporary, destination) {
let _ = std::fs::remove_file(&temporary);
if had_destination {
std::fs::rename(&backup, destination).map_err(|rollback_error| {
io::Error::new(
rollback_error.kind(),
format!(
"replace {}: {error}; restore {}: {rollback_error}",
destination.display(),
backup.display()
),
)
})?;
}
return Err(io::Error::new(
error.kind(),
format!("replace {} from {}: {error}", destination.display(), temporary.display()),
));
}
if had_destination {
if let Err(error) = std::fs::remove_file(&backup) {
println!(
"cargo:warning=generated header published but stale backup {} could not be removed: {error}",
backup.display()
);
}
}
Ok(())
}