use crate::utils::path_utils::normalize_windows_unc_path;
use cbindgen::Error;
use log::{debug, info};
use std::fs;
use std::path::Path;
pub(crate) struct CbindgenArgs<'a> {
pub rust_crate_dir: &'a Path,
pub c_struct_names: Vec<String>,
pub exclude_symbols: Vec<String>,
pub after_includes: String,
}
pub(crate) fn cbindgen(args: CbindgenArgs) -> anyhow::Result<String> {
let temp_c_output_file = tempfile::Builder::new().suffix(".h").tempfile()?;
cbindgen_to_file(args, temp_c_output_file.path())?;
let output_text = fs::read_to_string(temp_c_output_file.path())?;
drop(temp_c_output_file);
Ok(output_text)
}
fn cbindgen_to_file(args: CbindgenArgs, c_output_path: &Path) -> anyhow::Result<()> {
debug!(
"execute cbindgen rust_crate_dir={rust_crate_dir:?} c_output_path={c_output_path:?}",
rust_crate_dir = args.rust_crate_dir
);
let default_cbindgen_config = default_cbindgen_config();
let config = cbindgen::Config {
export: cbindgen::ExportConfig {
include: args.c_struct_names,
exclude: args.exclude_symbols,
..Default::default()
},
after_includes: Some(
args.after_includes + &default_cbindgen_config.after_includes.unwrap(),
),
..default_cbindgen_config
};
debug!("cbindgen config: {:#?}", config);
cbindgen_raw(config, args.rust_crate_dir, c_output_path)
}
pub(crate) fn default_cbindgen_config() -> cbindgen::Config {
cbindgen::Config {
after_includes: Some("typedef struct _Dart_Handle* Dart_Handle;".to_owned()),
language: cbindgen::Language::C,
sys_includes: vec![
"stdbool.h".to_string(),
"stdint.h".to_string(),
"stdlib.h".to_string(),
],
no_includes: true,
..Default::default()
}
}
pub(crate) fn cbindgen_raw(
config: cbindgen::Config,
rust_crate_dir: &Path,
c_output_path: &Path,
) -> anyhow::Result<()> {
let parsed_crate_dir = parse_crate_dir(rust_crate_dir)?;
debug!("cbindgen parsed_crate_dir={}", parsed_crate_dir);
#[allow(clippy::manual_inspect)]
let bindings = cbindgen::generate_with_config(parsed_crate_dir, config).map_err(|e| {
if let Error::ParseSyntaxError { src_path, .. } = &e {
let content =
fs::read_to_string(src_path).unwrap_or_else(|_| "CANNOT READ FILE".into());
info!("More information: src_path={src_path:?} content={content}");
}
e
})?;
bindings.write_to_file(c_output_path);
Ok(())
}
fn parse_crate_dir(rust_crate_dir: &Path) -> anyhow::Result<String> {
let canonical_path = Path::new(rust_crate_dir).canonicalize()?;
Ok(normalize_windows_unc_path(canonical_path.to_str().unwrap()).to_owned())
}
#[cfg(test)]
mod tests {
use super::{
cbindgen, default_cbindgen_config, normalize_windows_unc_path, parse_crate_dir,
CbindgenArgs,
};
use anyhow::Result;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_default_cbindgen_config() {
let config = default_cbindgen_config();
assert_eq!(config.language, cbindgen::Language::C);
assert_eq!(config.sys_includes, ["stdbool.h", "stdint.h", "stdlib.h"]);
assert!(config.no_includes);
assert_eq!(
config.after_includes.as_deref(),
Some("typedef struct _Dart_Handle* Dart_Handle;")
);
}
#[test]
fn test_parse_crate_dir_normalizes_existing_path() -> Result<()> {
let temp_dir = tempdir()?;
let crate_dir = temp_dir.path().join("crate");
fs::create_dir(&crate_dir)?;
assert_eq!(
parse_crate_dir(&crate_dir)?,
normalize_windows_unc_path(&crate_dir.canonicalize()?.to_string_lossy())
);
Ok(())
}
#[test]
fn test_cbindgen_generates_header_for_minimal_crate() -> Result<()> {
let temp_dir = tempdir()?;
let crate_dir = temp_dir.path();
fs::create_dir(crate_dir.join("src"))?;
fs::write(
crate_dir.join("Cargo.toml"),
"[package]\nname = \"cbindgen_test\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)?;
fs::write(
crate_dir.join("src/lib.rs"),
"#[no_mangle]\npub extern \"C\" fn answer() -> i32 { 42 }\n",
)?;
let output = cbindgen(CbindgenArgs {
rust_crate_dir: crate_dir,
c_struct_names: Vec::new(),
exclude_symbols: Vec::new(),
after_includes: "#include <dart_api.h>\n".to_owned(),
})?;
assert!(output.contains("#include <dart_api.h>"));
assert!(output.contains("typedef struct _Dart_Handle* Dart_Handle;"));
assert!(output.contains("int32_t answer(void);"));
Ok(())
}
}