use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
fn main() {
BuildScript::new().run();
}
struct BuildScript {
manifest_dir: String,
workspace_root: String,
mysql_src: String,
mysql_build: String,
out_dir: PathBuf,
}
impl BuildScript {
fn new() -> Self {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is always set by cargo");
let workspace_root = format!("{manifest_dir}/..");
let mysql_src = match env::var("MYSQL_SOURCE_DIR") {
Ok(v) => v,
Err(_) => format!("{workspace_root}/mysql-server"),
};
let mysql_build = match env::var("MYSQL_BUILD_DIR") {
Ok(v) => v,
Err(_) => format!("{workspace_root}/build/mysql"),
};
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is always set by cargo"));
Self {
manifest_dir,
workspace_root,
mysql_src,
mysql_build,
out_dir,
}
}
fn run(&self) {
println!("cargo:rerun-if-env-changed=MYSQL_HANDLER_REGEN_BINDINGS");
println!("cargo:rerun-if-env-changed=MYSQL_HANDLER_FROM_SOURCE");
println!("cargo:rerun-if-env-changed=MYSQL_HANDLER_ARCHIVE");
if env::var("MYSQL_HANDLER_REGEN_BINDINGS").is_ok() {
self.regen_bindings();
}
match env::var("MYSQL_HANDLER_FROM_SOURCE") {
Ok(_) => self.build_shim_from_source(),
Err(_) => {
if let Ok(archive) = env::var("MYSQL_HANDLER_ARCHIVE") {
self.install_prebuilt_shim(Path::new(&archive));
}
}
}
}
fn regen_bindings(&self) {
let header = format!("{}/shim/bindgen_input.h", self.manifest_dir);
println!("cargo:rerun-if-changed={header}");
println!("cargo:rerun-if-env-changed=MYSQL_SOURCE_DIR");
println!("cargo:rerun-if-env-changed=MYSQL_BUILD_DIR");
let license = fs::read_to_string(format!(
"{}/scripts/license-header.txt",
self.workspace_root
))
.expect("read scripts/license-header.txt");
let mut builder = bindgen::Builder::default();
for line in license.lines() {
let prefixed = if line.is_empty() {
"//".to_string()
} else {
format!("// {line}")
};
builder = builder.raw_line(prefixed);
}
builder = builder.raw_line("");
builder = builder
.raw_line("// Generated by bindgen via `make sys-bindings`. Do not edit by hand.");
let bindings = builder
.header(&header)
.clang_arg("-x")
.clang_arg("c++")
.clang_arg("-std=c++20")
.clang_arg(format!("-I{}/include", self.mysql_build))
.clang_arg(format!("-I{}/include", self.mysql_src))
.allowlist_var("HA_ERR_.*")
.allowlist_type("Table_flags")
.allowlist_type("thr_lock_type")
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate()
.expect("bindgen generation failed");
let out_path = PathBuf::from(&self.manifest_dir).join("src/sys_bindings.rs");
bindings
.write_to_file(&out_path)
.expect("write bindgen output to src/sys_bindings.rs");
}
fn build_shim_from_source(&self) {
Self::declare_rerun_for_tree(&format!("{}/shim", self.manifest_dir));
let dst = cmake::Config::new(format!("{}/shim", self.manifest_dir))
.define("MYSQL_SOURCE_DIR", &self.mysql_src)
.define("MYSQL_BUILD_DIR", &self.mysql_build)
.build_target("ha_rusty_shim")
.build();
Self::publish_staticlib_dir(&format!("{}/build", dst.display()));
}
fn install_prebuilt_shim(&self, archive: &Path) {
assert!(
archive.is_file(),
"MYSQL_HANDLER_ARCHIVE must point to a readable .a.gz file: {}",
archive.display()
);
let prebuilt_dir = self.out_dir.join("prebuilt");
fs::create_dir_all(&prebuilt_dir).expect("create prebuilt staticlib dir under OUT_DIR");
let staticlib_path = prebuilt_dir.join("libha_rusty_shim.a");
Self::extract_gz(archive, &staticlib_path);
Self::publish_staticlib_dir(&prebuilt_dir.display().to_string());
}
fn extract_gz(gz_path: &Path, dest: &Path) {
let gz_file = fs::File::open(gz_path).expect("open MYSQL_HANDLER_ARCHIVE for reading");
let mut decoder = flate2::read::GzDecoder::new(io::BufReader::new(gz_file));
let mut out_file =
fs::File::create(dest).expect("create extracted staticlib under OUT_DIR/prebuilt");
io::copy(&mut decoder, &mut out_file).expect("decompress MYSQL_HANDLER_ARCHIVE");
}
fn publish_staticlib_dir(dir: &str) {
println!("cargo:staticlib-dir={dir}");
}
fn declare_rerun_for_tree(dir: &str) {
println!("cargo:rerun-if-changed={dir}");
let entries = match fs::read_dir(dir) {
Ok(e) => e,
Err(e) => {
println!("cargo:warning=skip rerun for {dir}: {e}");
return;
}
};
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.starts_with('.')
|| matches!(name_str.as_ref(), "build" | "target" | "CMakeFiles")
{
continue;
}
let is_dir = match entry.file_type() {
Ok(t) => t.is_dir(),
Err(_) => false,
};
if is_dir {
Self::declare_rerun_for_tree(&path.to_string_lossy());
} else {
let watch = matches!(
path.extension().and_then(|s| s.to_str()),
Some("cc" | "cpp" | "hpp" | "h")
) || name_str == "CMakeLists.txt";
if watch {
println!("cargo:rerun-if-changed={}", path.to_string_lossy());
}
}
}
}
}