godot-bindings 0.5.0

Internal crate used by godot-rust
/*
 * Copyright (c) godot-rust; Bromeon and contributors.
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

use std::env;
use std::path::Path;

pub(crate) fn generate_rust_binding(in_h_path: &Path, out_rs_path: &Path) {
    let c_header_path = in_h_path.display().to_string();

    // Default behavior of bindgen is to invalidate the built crate whenever any of the included header files changed. This is sensible,
    // but in our case, listening to changes on files that are generated by this build step cause an infinite loop with cargo watch of:
    //     build -> detect change -> rebuild -> detect change -> ...
    // println!("cargo:rerun-if-changed={}", c_header_path);
    //
    // Without `rerun_on_header_files(false)`, the following command causes repeated recompilation of godot-ffi onward:
    //     cargo build -p itest --features godot/api-custom
    //
    // This is non-trivial to fix and isn't planned at the moment, see https://github.com/godot-rust/gdext/issues/281.
    // If you have an idea to address this without too invasive changes, please comment on that issue.
    let cargo_cfg = bindgen::CargoCallbacks::new().rerun_on_header_files(false);

    let builder = bindgen::Builder::default()
        .header(c_header_path)
        .parse_callbacks(Box::new(cargo_cfg))
        .prepend_enum_name(false)
        // Bindgen can generate wrong size checks for types defined as `__attribute__((aligned(__alignof__(struct {...}))))`,
        // which is how clang defines max_align_t: https://clang.llvm.org/doxygen/____stddef__max__align__t_8h_source.html.
        // Size checks seems to be fine on all the targets but `wasm32-unknown-emscripten`, disallowing web builds.
        // We don't use `max_align_t` anywhere, so blacklisting it until upstream fixes the problem seems to be the most sane solution.
        // See: https://github.com/rust-lang/rust-bindgen/issues/3295.
        .blocklist_type("max_align_t");

    std::fs::create_dir_all(
        out_rs_path
            .parent()
            .expect("bindgen output file has parent dir"),
    )
    .expect("create bindgen output dir");

    let bindings = configure_platform_specific(builder)
        .generate()
        .unwrap_or_else(|err| {
            panic!(
                "bindgen generate failed\n    c: {}\n   rs: {}\n  err: {}\n",
                in_h_path.display(),
                out_rs_path.display(),
                err
            )
        });

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    bindings.write_to_file(out_rs_path).unwrap_or_else(|err| {
        panic!(
            "bindgen write failed\n    c: {}\n   rs: {}\n  err: {}\n",
            in_h_path.display(),
            out_rs_path.display(),
            err
        )
    });
}

//#[cfg(target_os = "macos")] #[cfg_attr(published_docs, doc(cfg(target_os = "macos")))]
fn configure_platform_specific(builder: bindgen::Builder) -> bindgen::Builder {
    // On macOS arm64 architecture, we currently get the following error. Tried using different LLVM versions.
    // Not clear if bindgen can be configured in a better way.
    //  error: linking with `cc` failed: signal: 6 (SIGABRT)
    //   = note: dyld[6080]: weak-def symbol not found '__ZnwmSt19__type_descriptor_t'weak-def symbol not found '__ZnamSt19__type_descriptor_t'

    let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
    if target_vendor == "apple" {
        eprintln!("Build selected for macOS.");
        let path = env::var("LLVM_PATH").expect("env var 'LLVM_PATH' not set");

        builder
            .clang_arg("-I")
            // .clang_arg(format!("{path}/include"))
            .clang_arg(apple_include_path().expect("apple include path"))
            .clang_arg("-L")
            .clang_arg(format!("{path}/lib"))
            .clang_arg("-Wl,-flat_namespace,-undefined,dynamic_lookup")
    } else {
        eprintln!("Build selected for Linux/Windows.");
        builder
    }
}

fn apple_include_path() -> Result<String, std::io::Error> {
    use std::process::Command;

    let target = std::env::var("TARGET").unwrap();
    let platform = if target.contains("apple-darwin") {
        "macosx"
    } else if target == "x86_64-apple-ios" || target == "aarch64-apple-ios-sim" {
        "iphonesimulator"
    } else if target == "aarch64-apple-ios" {
        "iphoneos"
    } else {
        panic!("not building for macOS or iOS");
    };

    // run `xcrun --sdk iphoneos --show-sdk-path`
    let output = Command::new("xcrun")
        .args(["--sdk", platform, "--show-sdk-path"])
        .output()?
        .stdout;
    let prefix = std::str::from_utf8(&output)
        .expect("invalid output from `xcrun`")
        .trim_end();

    let suffix = "usr/include";
    let directory = format!("{prefix}/{suffix}");

    Ok(directory)
}

// #[cfg(not(target_os = "macos"))]
// fn configure_platform_specific(builder: Builder) -> Builder {
//     println!("Build selected for Linux/Windows.");
//     builder
// }

/*fn rerun_if_any_changed(paths: &Vec<PathBuf>){
    for path in paths {
        println!("cargo:rerun-if-changed={}", path.display());
    }
}*/