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 = "9.0.3";
struct VarnishInfo {
bindings: PathBuf,
varnish_paths: Vec<PathBuf>,
version: String,
}
impl VarnishInfo {
fn parse(bindings: PathBuf, varnish_paths: Vec<PathBuf>, version: String) -> Self {
emit_version_cfgs(&version);
Self {
bindings,
varnish_paths,
version,
}
}
}
fn emit_version_cfgs(version: &str) {
if version == "trunk" {
println!("cargo::rustc-cfg=varnishsys_90_sslflags");
return;
}
let ver = semver::Version::parse(version)
.unwrap_or_else(|_| panic!("varnishapi invalid version: {version}"));
if ver >= semver::Version::new(9, 0, 0) {
println!("cargo::rustc-cfg=varnishsys_90_sslflags");
} else if ver < semver::Version::new(8, 0, 0) {
println!(
"cargo::warning=Varnish {version} is not supported and may not work with this crate"
);
}
}
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);
}
}
fn detect_varnish() -> Option<VarnishInfo> {
println!("cargo::rustc-check-cfg=cfg(varnishsys_90_sslflags)");
let bindings =
PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable must be set"))
.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 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()
.expect("varnish include path must be valid UTF-8")
)
}))
.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));
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).expect("failed to read generated bindings file");
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 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)
.expect("failed to copy bindings file for docs.rs");
println!("cargo::metadata=version_number={BINDINGS_FILE_VER}");
emit_version_cfgs(BINDINGS_FILE_VER);
None
} else {
panic!("pkg_config failed to find varnishapi, make sure it is installed: {e:?}");
}
}
}
}