alef 0.81.0

Opinionated polyglot binding generator for Rust libraries
Documentation
// 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 {
        return Ok(());
    }
    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(())
}