extern crate bindgen;
use regex::Regex;
use std::panic::catch_unwind;
use std::path::{Path, PathBuf};
use std::{env, io};
use bindgen::callbacks::{IntKind, ParseCallbacks};
type FeatureFilter<'a, T> = (&'a [T], Option<&'a [&'a str]>);
fn filter_features<'a, T: 'a>(features: impl Iterator<Item = &'a FeatureFilter<'a, T>>) -> impl Iterator<Item = &'a T> {
features
.filter(|&(.., feature)| match feature {
Some(names) => names
.iter()
.map(|name| env::var("CARGO_FEATURE_".to_string() + &(name.to_uppercase()))) .any(|f| f.is_ok()), None => true, })
.flat_map(|&(x, ..)| x)
}
const SOURCE_FILES: &[FeatureFilter<&str>] = &[
(&["src/c/defaults.c", "src/c/strings.c"], None), (&["src/c/exits.c"], Some(&["exits"])), (&["src/c/pcf.c"], Some(&["pcf"])), ];
const HEADER_FILES: &[FeatureFilter<&str>] = &[
(
&[
"cmqc.h", "cmqxc.h", "cmqstrc.h", ],
None,
), (&["cmqbc.h"], Some(&["mqai"])), (&["cmqec.h", "cmqcfc.h"], Some(&["pcf"])), ];
const FUNCTIONS: &[FeatureFilter<&str>] = &[(&["MQ.+"], None), (&["mq.+"], Some(&["mqai"]))];
const TYPES: &[FeatureFilter<&str>] = &[
(
&[
"MQMD", "MQMDE", "MQMD1", "MQMD2", "MQPD", "MQIMPO", "MQMHBO", "MQBO", "MQDMHO", "MQCMHO", "MQSRO", "MQSD",
"MQGMO", "MQPMO", "MQOD", "MQCNO", "MQCD", "MQCSP", "MQSCO", "MQBNO", "MQAIR", "MQBMHO", "MQCBC", "MQCBD",
"MQCHARV", "MQCIH", "MQCTLO", "MQDH", "MQDLH", "MQDMPO", "MQIIH", "MQOR", "MQRFH", "MQRFH2", "MQRMH",
"MQRR", "MQSMPO", "MQSTS", "MQTM", "MQTMC2", "MQWIH", "MQXQH",
],
None,
),
(
&[
"MQCFH", "MQCFBF", "MQCFBS", "MQCFGR", "MQCFIF", "MQCFIL", "MQCFIL64", "MQCFIN", "MQCFIN64", "MQCFSF",
"MQCFSL", "MQCFST", "MQEPH", "MQZED", "MQZAC", "MQZAD", "MQZFP", "MQZIC"
],
Some(&["pcf"]),
),
(
&[
"MQACH", "MQAXC", "MQAXP", "MQCXP", "MQDXP", "MQNXP", "MQPBC", "MQPSXP", "MQSBC", "MQWCR", "MQWDR",
"MQWDR1", "MQWDR2", "MQWQR", "MQWQR1", "MQWQR2", "MQWQR3", "MQWQR4", "MQWXP", "MQWXP1", "MQWXP2", "MQWXP3",
"MQWXP4", "MQXEPO", "MQIEP",
],
Some(&["exits"]),
),
];
const DEF_CONST: &[(&[&str], IntKind)] = &[
(
&["^MQ.*_ERROR$"], IntKind::Custom {
name: "MQLONG",
is_signed: true,
},
),
(
&[".+_LENGTH(_.)?"], IntKind::Custom {
name: "usize",
is_signed: false,
},
),
(
&["^MQHM_.+"], IntKind::Custom {
name: "MQHMSG",
is_signed: true,
},
),
(
&["^MQHO_.+"], IntKind::Custom {
name: "MQHOBJ",
is_signed: true,
},
),
(
&["^MQHC_.+"], IntKind::Custom {
name: "MQHCONN",
is_signed: true,
},
),
(&["^MQ.+_MASK$"], IntKind::U32), (
&["^MQ_?[A-Z]{2,12}_.+"], IntKind::Custom {
name: "MQLONG",
is_signed: true,
},
),
];
#[derive(Debug)]
struct MQCTypeChooser(Vec<(Vec<Regex>, IntKind)>);
impl ParseCallbacks for MQCTypeChooser {
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
let Self(chooser) = self;
chooser
.iter()
.find(|&(matchers, ..)| matchers.iter().any(|r| r.is_match(name)))
.map(|&(.., int_kind)| int_kind)
}
}
fn default_mq_home() -> &'static str {
env::var("CARGO_CFG_WINDOWS").map(|_| "c:/Program Files/IBM/MQ").unwrap_or("/opt/mqm")
}
fn link_lib() -> &'static str {
env::var("CARGO_CFG_WINDOWS").map(|_| "mqm").unwrap_or("mqm_r")
}
fn inc_path() -> &'static str {
env::var("CARGO_CFG_WINDOWS").map(|_| "tools/c/include").unwrap_or("inc")
}
fn lib_path() -> &'static str {
env::var("CARGO_CFG_WINDOWS").map(|_| "tools/lib64").unwrap_or("lib64")
}
fn build_c(mq_inc_path: &PathBuf) -> Result<(), io::Error> {
let sources = filter_features(SOURCE_FILES.iter()).collect::<Vec<_>>();
for source in &sources {
println!("cargo:rerun-if-changed={source}")
}
catch_unwind(|| {
cc::Build::new()
.static_flag(false)
.flag_if_supported("-nostartfiles")
.include(mq_inc_path)
.files(sources)
.warnings(true)
.compile("mq_functions")
})
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Failed to compile c files"))
}
fn generate_bindings(mq_inc_path: &Path) -> Result<bindgen::Bindings, bindgen::BindgenError> {
let chooser = MQCTypeChooser(
DEF_CONST
.iter()
.map(|&(re_list, kind)| {
(
re_list
.iter()
.map(|re| Regex::new(re).expect("\"{re}\" is not valid"))
.collect(),
kind,
)
})
.collect(),
);
let builder = bindgen::builder()
.clang_arg(format!("-I{}", mq_inc_path.display()))
.generate_cstr(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.parse_callbacks(Box::new(chooser))
.allowlist_var(".*");
let builder = filter_features(HEADER_FILES.iter())
.fold(builder, |builder, header| {
builder.header(mq_inc_path.join(header).to_str().expect("\"{header}\" is not valid"))
});
let builder = filter_features(TYPES.iter()).fold(builder, |builder, &struc| builder.allowlist_type(struc));
let builder = filter_features(FUNCTIONS.iter()).fold(builder, |builder, &func| builder.allowlist_function(func));
builder.generate()
}
fn main() -> Result<(), io::Error> {
let mq_home_path = PathBuf::from(env::var("MQ_HOME").unwrap_or_else(|_| default_mq_home().to_string()));
let out_path = PathBuf::from(env::var("OUT_DIR").map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?); let mq_inc_path = mq_home_path.join(inc_path());
if env::var("CARGO_FEATURE_LINK").is_ok() {
let mq_lib_path = mq_home_path.join(lib_path());
println!("cargo:rustc-link-search={}", mq_lib_path.display());
println!("cargo:rustc-link-lib=dylib={}", link_lib());
}
build_c(&mq_inc_path)?;
generate_bindings(&mq_inc_path)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
.write_to_file(out_path.join("bindings.rs"))
}