use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::{env, fs};
use bindgen_helpers as bindgen;
use bindgen_helpers::{rename_enum, Renamer};
static BINDINGS_FILE: &str = "bindings.for-docs";
static BINDINGS_FILE_VER: &str = "7.7.1";
struct VarnishInfo {
bindings: PathBuf,
varnish_paths: Vec<PathBuf>,
version: String,
defines: Vec<&'static str>,
}
impl VarnishInfo {
fn parse(bindings: PathBuf, varnish_paths: Vec<PathBuf>, version: String) -> Self {
let (major, minor) = parse_version(&version);
if major == 7 && minor < 6 {
println!("cargo::rustc-cfg=varnishsys_7_5_objcore_init");
}
if major < 7 {
println!("cargo::rustc-cfg=varnishsys_6");
}
if major < 6 || major > 7 {
println!(
"cargo::warning=Varnish {version} is not supported and may not work with this crate"
);
}
let mut defines = vec![];
if major < 7 {
defines.push("VARNISH_RS_6_0");
} else {
defines.push("VARNISH_RS_HTTP_CONN");
}
defines.push("VARNISH_RS_ALLOC_VARIADIC");
Self {
bindings,
varnish_paths,
version,
defines,
}
}
}
impl Display for VarnishInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.version)
}
}
fn main() {
if let Some(info) = &detect_varnish() {
generate_bindings(info);
build_c_wrapper(info);
}
}
fn detect_varnish() -> Option<VarnishInfo> {
println!("cargo::rustc-check-cfg=cfg(varnishsys_7_5_objcore_init)");
println!("cargo::rustc-check-cfg=cfg(varnishsys_6)");
let bindings = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
println!("cargo:rerun-if-env-changed=VARNISH_INCLUDE_PATHS");
let (varnish_paths, version) = find_include_dir(&bindings)?;
println!("cargo::metadata=version_number={version}");
Some(VarnishInfo::parse(bindings, varnish_paths, version))
}
fn generate_bindings(info: &VarnishInfo) {
let mut ren = Renamer::default();
rename_enum!(ren, "VSL_tag_e" => "VslTag", remove: "SLT_"); rename_enum!(ren, "boc_state_e" => "BocState", remove: "BOS_"); rename_enum!(ren, "director_state_e" => "DirectorState", remove: "DIR_S_", "HDRS" => "Headers"); rename_enum!(ren, "gethdr_e" => "GetHeader", remove: "HDR_"); rename_enum!(ren, "sess_attr" => "SessionAttr", remove: "SA_"); rename_enum!(ren, "lbody_e" => "Body", remove: "LBODY_"); rename_enum!(ren, "task_prio" => "TaskPriority", remove: "TASK_QUEUE_"); rename_enum!(ren, "vas_e" => "Vas", remove: "VAS_"); rename_enum!(ren, "vcl_event_e" => "VclEvent", remove: "V(CL|DI)_EVENT_"); rename_enum!(ren, "vcl_func_call_e" => "VclFuncCall", remove: "VSUB_"); rename_enum!(ren, "vcl_func_fail_e" => "VclFuncFail", remove: "VSUB_E_"); rename_enum!(ren, "vdp_action" => "VdpAction", remove: "VDP_"); rename_enum!(ren, "vfp_status" => "VfpStatus", remove: "VFP_");
println!("cargo::rustc-link-lib=varnishapi");
println!("cargo::rerun-if-changed=c_code/wrapper.h");
let mut bindings_builder = bindgen::Builder::default()
.header("c_code/wrapper.h")
.blocklist_item("FP_.*")
.blocklist_item("FILE")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.clang_args(
info.varnish_paths
.iter()
.map(|i| format!("-I{}", i.to_str().unwrap())),
)
.ctypes_prefix("::std::ffi")
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.generate_cstr(true)
.type_alias("VCL_VOID")
.type_alias("VCL_INSTANCE")
.new_type_alias("VCL_.*")
.new_type_alias("vtim_.*") .rustified_non_exhaustive_enum(ren.get_regex_str())
.parse_callbacks(Box::new(ren));
for define in &info.defines {
bindings_builder = bindings_builder.clang_args(&["-D", define]);
}
let bindings = bindings_builder
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(&info.bindings)
.expect("Couldn't write bindings!");
let generated = fs::read_to_string(&info.bindings).unwrap();
let checked_in = fs::read_to_string(BINDINGS_FILE).unwrap_or_default();
if generated != checked_in {
println!(
"cargo::warning=Generated bindings from Varnish {info} differ from checked-in {BINDINGS_FILE}. Update with cp {} varnish-sys/{BINDINGS_FILE}",
info.bindings.display()
);
} else if BINDINGS_FILE_VER != info.version {
println!(
r#"cargo::warning=Generated bindings **version** from Varnish {info} differ from checked-in {BINDINGS_FILE}. Update `build.rs` file with BINDINGS_FILE_VER = "{info}""#
);
}
}
fn build_c_wrapper(info: &VarnishInfo) {
let mut builder = cc::Build::new();
for define in &info.defines {
builder.define(define, None);
}
builder
.file("c_code/wrapper.c")
.includes(&info.varnish_paths)
.compile("varnish_wrapper");
}
fn find_include_dir(out_path: &PathBuf) -> Option<(Vec<PathBuf>, String)> {
if let Ok(s) = env::var("VARNISH_INCLUDE_PATHS") {
println!("cargo::warning=Using VARNISH_INCLUDE_PATHS='{s}' env var, and assume it is the latest supported version {BINDINGS_FILE_VER}");
return Some((
s.split(':').map(PathBuf::from).collect(),
BINDINGS_FILE_VER.into(), ));
}
let pkg = pkg_config::Config::new();
match pkg.probe("varnishapi") {
Ok(l) => Some((l.include_paths, l.version)),
Err(e) => {
if env::var("DOCS_RS").is_ok() {
eprintln!("libvarnish not found, using saved bindings for the doc.rs: {e}");
fs::copy(BINDINGS_FILE, out_path).unwrap();
println!("cargo::metadata=version_number={BINDINGS_FILE_VER}");
None
} else {
panic!("pkg_config failed to find varnishapi, make sure it is installed: {e:?}");
}
}
}
}
fn parse_version(version: &str) -> (u32, u32) {
let mut parts = version.split('.');
(
parse_next_int(&mut parts, "major"),
parse_next_int(&mut parts, "minor"),
)
}
fn parse_next_int(parts: &mut std::str::Split<char>, name: &str) -> u32 {
let val = parts
.next()
.unwrap_or_else(|| panic!("varnishapi invalid version {name}"));
val.parse::<u32>()
.unwrap_or_else(|_| panic!("varnishapi invalid version - {name} value is '{val}'"))
}