use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
macro_rules! opaque_types {
($builder:ident, $($name:literal),+ $(,)?) => {
$(
$builder = $builder
.allowlist_type(concat!("^", $name, "$"))
.opaque_type(concat!("^", $name, "$"));
)+
};
}
macro_rules! allowlist_types {
($builder:ident, $($name:literal),+ $(,)?) => {
$(
$builder = $builder
.allowlist_type(concat!("^", $name, "$"));
)+
};
}
macro_rules! allowlist_functions {
($builder:ident, $($name:literal),+ $(,)?) => {
$(
$builder = $builder
.allowlist_function(concat!("^", $name, "$"));
)+
};
}
fn generate_ffi(spdk_include_dir: &Path, out_dir: &Path) {
let mut builder = bindgen::Builder::default()
.header("c_src/bindgen.h")
.clang_arg(format!("-I{}", spdk_include_dir.display()))
.clang_arg("-std=gnu11")
.prepend_enum_name(false)
.allowlist_recursively(false);
opaque_types!(
builder,
"spdk_thread",
"spdk_poller",
"spdk_io_channel",
"spdk_bdev_desc",
"spdk_bdev",
"spdk_bdev_io",
"spdk_app_opts",
"spdk_json_val",
"spdk_cpuset",
"spdk_bdev_alias",
"spdk_bdev_name",
"spdk_bdev_module",
"spdk_bdev_module_claim",
"spdk_bdev_qos",
"spdk_spinlock",
"spdk_bdev_io_stat",
"spdk_bdev_io_block_params",
"spdk_bdev_io_reset_params",
"spdk_bdev_io_abort_params",
"spdk_bdev_io_nvme_passthru_params",
"spdk_bdev_io_zone_mgmt_params",
"lba_range_tailq_t",
"bdev_io_tailq_t",
"lba_range",
);
allowlist_types!(
builder,
"iovec",
"spdk_dif_type",
"spdk_bdev_io_type",
"spdk_msg_fn",
"spdk_poller_fn",
"spdk_bdev_status",
"spdk_bdev_claim_type",
"spdk_bdev_unregister_cb",
"spdk_app_shutdown_cb",
"spdk_bdev_io_completion_cb",
"spdk_bdev_io_status",
"SpdkBdevOptsC",
);
allowlist_functions!(
builder,
"spdk_thread_create",
"spdk_thread_send_msg",
"spdk_get_thread",
"spdk_thread_get_id",
"spdk_thread_is_exited",
"spdk_thread_is_running",
"spdk_thread_exit",
"spdk_bdev_desc_get_bdev",
"spdk_bdev_close",
"spdk_bdev_io_get_md_buf",
"spdk_get_io_channel",
"spdk_bdev_get_md_size",
"spdk_bdev_is_md_separate",
"spdk_bdev_get_io_channel",
"spdk_put_io_channel",
"spdk_io_channel_ref",
"spdk_io_channel_get_ref_count",
"spdk_bdev_io_type_supported",
"spdk_bdev_get_dif_type",
"spdk_bdev_get_block_size",
"spdk_bdev_readv_blocks",
"spdk_bdev_writev_blocks",
"spdk_bdev_io_complete",
"spdk_bdev_free_io",
"spdk_poller_register",
"spdk_poller_unregister",
"spdk_poller_pause",
"spdk_poller_resume",
"spdk_cpuset_zero",
"spdk_cpuset_set_cpu",
"spdk_env_get_core_count",
"spdk_dma_malloc",
"spdk_dma_zmalloc",
"spdk_dma_free",
"u_spdk_app_.*",
"u_spdk_cpuset_.*",
"u_spdk_io_channel_.*",
"u_spdk_bdev_.*",
"u_io_channel_.*",
"u_bdev_.*",
"u_json_.*",
"rsu_bdev_.*",
"rsu_io_.*",
);
let ffi = builder
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate C FFI");
ffi.write_to_file(out_dir.join("ffi.rs"))
.expect("Unable to write C FFI");
}
fn build_shim(spdk_include_dir: &Path) {
let mut build = cc::Build::new();
build
.file("c_src/bdev.c")
.file("c_src/json.c")
.file("c_src/shim.c")
.file("c_src/util.c")
.flag("-isystem")
.flag(spdk_include_dir.to_str().unwrap())
.flag("-std=gnu11")
.cargo_metadata(false);
build
.try_compile("ironspdkshim")
.expect("Failed to compile ironspdk C shim");
}
fn resolve_spdk_include() -> PathBuf {
if let Ok(dir) = env::var("SPDK") {
let p = PathBuf::from(dir).join("include");
if p.exists() {
return p;
} else {
panic!("SPDK/include not found");
}
}
let output = Command::new("pkg-config")
.args(["--cflags-only-I", "spdk"])
.output()
.expect("pkg-config not found");
if output.status.success() {
let stdout = String::from_utf8(output.stdout).unwrap();
for token in stdout.split_whitespace() {
if let Some(path) = token.strip_prefix("-I") {
return PathBuf::from(path);
}
}
}
panic!(
"\n\nError: could not find SPDK headers\n\
------------------------------------------------\n\
Options to fix this:\n\
1. Set the SPDK environment variable to the root of your SPDK source tree\n\
2. Install SPDK system-wide and ensure 'pkg-config' can find it\n\
------------------------------------------------\n"
);
}
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let ironspdk_include_dir = manifest_dir.join("c_src");
println!("cargo:include={}", ironspdk_include_dir.display());
println!(
"cargo:rustc-env=IRONSPDK_INCLUDE_DIR={}",
ironspdk_include_dir.display()
);
let spdk_include_dir = resolve_spdk_include();
build_shim(&spdk_include_dir);
let out_dir = env::var("OUT_DIR").unwrap();
println!("cargo:metadata={}", out_dir);
let out_dir_path = PathBuf::from(out_dir);
generate_ffi(&spdk_include_dir, &out_dir_path);
println!("cargo::rerun-if-changed=c_src/");
println!("cargo::rerun-if-changed=src/");
println!("cargo::rerun-if-env-changed=SPDK");
println!("cargo::rerun-if-env-changed=CARGO_MANIFEST_DIR");
}