mysqlclient-sys 0.4.2

Auto-generated rust bindings for libmysqlclient
Documentation
use std::env;
use std::path::PathBuf;
use std::process::Command;

const PKG_CONFIG_MYSQL_LIB: &str = "mysqlclient";
const PKG_CONFIG_MARIADB_LIB: &str = "libmariadb";
#[cfg(target_env = "msvc")]
const VCPKG_MYSQL_LIB: &str = "libmysql";
#[cfg(target_env = "msvc")]
const VCPKG_MARIADB_LIB: &str = "libmariadb";

fn main() {
    if cfg!(feature = "bundled") {
        parse_version("9.0.1");
        return;
    }
    let target = std::env::var("TARGET")
        .expect("Set by cargo")
        .to_ascii_uppercase()
        .replace("-", "_");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_VERSION");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_INCLUDE_DIR");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_INCLUDE_DIR_{target}");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIB");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIB_DIR");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIB_DIR_{target}");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIBNAME");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIBNAME_{target}");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_STATIC");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_VERSION_{target}");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_LIB_{target}");
    println!("cargo::rerun-if-env-changed=MYSQLCLIENT_STATIC_{target}");
    if cfg!(feature = "buildtime_bindgen") {
        autogen_bindings(&target);
    }
    let libname = env::var("MYSQLCLIENT_LIBNAME")
        .or_else(|_| env::var(format!("MYSQLCLIENT_LIBNAME_{target}")))
        .unwrap_or("mysqlclient".to_string());
    let link_specifier = if env::var("MYSQLCLIENT_STATIC")
        .or(env::var(format!("MYSQLCLIENT_STATIC_{target}")))
        .is_ok()
    {
        "static="
    } else {
        ""
    };
    if let Ok(lib) = pkg_config::probe_library(PKG_CONFIG_MYSQL_LIB)
        .or_else(|_| pkg_config::probe_library(PKG_CONFIG_MARIADB_LIB))
    {
        // rebuild if users upgraded the mysql library on their machine
        for link in lib.link_paths {
            println!("cargo::rerun-if-changed={}", link.display());
        }
        // pkg_config did everything but the version flags for us
        parse_version(&lib.version);
        return;
    } else if try_vcpkg() {
        // vcpkg did everything for us
        if let Ok(version) =
            env::var("MYSQLCLIENT_VERSION").or(env::var(format!("MYSQLCLIENT_VERSION_{target}")))
        {
            parse_version(&version);
            return;
        }
    } else if let Ok(path) =
        env::var("MYSQLCLIENT_LIB_DIR").or(env::var(format!("MYSQLCLIENT_LIB_DIR_{target}")))
    {
        println!("cargo:rustc-link-search=native={path}");
        println!("cargo:rustc-link-lib={link_specifier}{libname}");
        if let Ok(version) =
            env::var("MYSQLCLIENT_VERSION").or(env::var(format!("MYSQLCLIENT_VERSION_{target}")))
        {
            parse_version(&version);
            return;
        }
    } else if let Some(output) = mysql_config_variable("--libs") {
        let parts = output.split_ascii_whitespace();
        for part in parts {
            if let Some(lib) = part.strip_prefix("-l") {
                println!("cargo:rustc-link-lib={link_specifier}{lib}");
            } else if let Some(path) = part.strip_prefix("-L") {
                println!("cargo:rustc-link-search=native={path}");
            } else if let Some(path) = part.strip_prefix("-R") {
                println!("cargo:rustc-link-arg=-Wl,-R{path}");
            } else {
                panic!("Unexpected output from mysql_config: `{output}`");
            }
        }

        if let Some(version) = mysql_config_variable("--version") {
            parse_version(&version);
            return;
        }
    }
    panic!(
        "Did not find a compatible version of libmysqlclient.\n\
            Ensure that you installed one and teached mysqlclient-sys how to find it\n\
            You have the following options for that:\n\
            \n\
            * Use `pkg_config` to automatically detect the right location\n\
            * Use vcpkg to automatically detect the right location. \n\
              You also need to set `MYSQLCLIENT_VERSION` to specify which\n\
              version of libmysqlclient you are using\n\
            * Set the `MYSQLCLIENT_LIB_DIR` and `MYSQLCLIENT_VERSION` environment \n\
              variables to point the compiler to the right directory and specify \n\
              which version is used\n\
            * Make the `mysql_config` binary avaible in the environment that invokes\n\
              the compiler"
    );
}

fn mysql_config_variable(var_name: &str) -> Option<String> {
    Command::new("mysql_config")
        .arg(var_name)
        .output()
        .into_iter()
        .filter(|output| output.status.success())
        .flat_map(|output| String::from_utf8(output.stdout).ok())
        .map(|output| output.trim().to_string())
        .next()
}

#[derive(Clone, Copy, Debug)]
enum MysqlVersion {
    Mysql5,
    Mysql80,
    Mysql83,
    Mysql84,
    Mysql90,
    MariaDb10,
}

impl MysqlVersion {
    const ALL: &'static [Self] = &[
        Self::Mysql5,
        Self::Mysql80,
        Self::Mysql83,
        Self::Mysql84,
        Self::Mysql90,
        Self::MariaDb10,
    ];

    fn as_cfg(&self) -> &'static str {
        match self {
            MysqlVersion::Mysql5 => "mysql_5_7_x",
            MysqlVersion::Mysql80 => "mysql_8_0_x",
            MysqlVersion::Mysql83 => "mysql_8_3_x",
            MysqlVersion::Mysql84 => "mysql_8_4_x",
            MysqlVersion::Mysql90 => "mysql_9_0_x",
            MysqlVersion::MariaDb10 => "mariadb_10_x",
        }
    }

    fn parse_version(version: &str) -> Option<Self> {
        // ubuntu/debian packages use the following package versions:
        // libmysqlclient20 -> 5.7.x
        // libmysqlclient21 -> 8.0.x
        // libmysqlclient23 -> 8.3.0
        // libmysqlclient24 -> 8.4.0 or 9.0
        // libmariadb-dev 3.3.8 -> mariadb 10.x
        // Linux version becomes the full SONAME like 21.3.2 but MacOS is just the
        // major.
        if version.starts_with("5.7") || version.starts_with("20.") || version == "20" {
            Some(Self::Mysql5)
        } else if version.starts_with("8.0") || version.starts_with("21.") || version == "21" {
            Some(Self::Mysql80)
        } else if version.starts_with("8.3") || version.starts_with("23.") || version == "23" {
            Some(Self::Mysql83)
        } else if version.starts_with("8.4") || version.starts_with("24.0") || version == "24" {
            Some(Self::Mysql84)
        } else if version.starts_with("9.0") || version.starts_with("24.1") {
            Some(Self::Mysql90)
        } else if version.starts_with("10.")
            || version.starts_with("11.")
            || version.starts_with("3.")
            || version == "3"
        {
            Some(Self::MariaDb10)
        } else {
            None
        }
    }
}

fn parse_version(version_str: &str) {
    use MysqlVersion::*;

    for v in MysqlVersion::ALL {
        println!("cargo::rustc-check-cfg=cfg({})", v.as_cfg());
    }
    let version = MysqlVersion::parse_version(version_str);

    let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").expect("Set by cargo");
    let is_windows = std::env::var("CARGO_CFG_WINDOWS").is_ok();
    let ptr_size = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH").expect("Set by cargo");
    let out_dir = std::env::var("OUT_DIR").expect("Set by cargo");
    let mut bindings_target = PathBuf::from(out_dir);
    bindings_target.push("bindings.rs");

    if let Some(version) = version {
        println!("cargo:rustc-cfg={}", version.as_cfg());
    }

    let bindings_path = match (version, target_arch.as_str(), ptr_size.as_str(), is_windows) {
        // if we use bindgen to generate bindings
        // we don't want to copy anything
        _ if cfg!(feature = "buildtime_bindgen") => {
            return;
        }
        (Some(Mysql5), "x86_64" | "aarch64", "64", false) => "bindings_5_7_42_x86_64_linux.rs",
        (Some(Mysql80), "x86_64" | "aarch64", "64", false) => "bindings_8_0_36_x86_64_linux.rs",
        (Some(Mysql80), "x86" | "arm", "32", false) => "bindings_8_0_37_i686_linux.rs",
        (Some(Mysql80), "x86_64", "64", true) => "bindings_8_0_36_x86_64_windows.rs",
        (Some(Mysql80), "x86", "32", true) => "bindings_8_0_36_i686_windows.rs",
        (Some(Mysql83), "x86_64" | "aarch64", "64", false) => "bindings_8_3_0_x86_64_linux.rs",
        (Some(Mysql83), "x86_64", "64", true) => "bindings_8_3_0_x86_64_windows.rs",
        (Some(Mysql83), "x86", "32", true) => "bindings_8_3_0_i686_windows.rs",
        (Some(Mysql84), "x86_64" | "aarch64", "64", false) => "bindings_8_4_0_x86_64_linux.rs",
        (Some(Mysql84), "x86" | "arm", "32", false) => "bindings_8_4_0_i686_linux.rs",
        (Some(Mysql84), "x86_64", "64", true) => "bindings_8_4_0_x86_64_windows.rs",
        (Some(Mysql84), "x86", "32", true) => "bindings_8_4_0_i686_windows.rs",
        (Some(Mysql90), "x86_64" | "aarch64", "64", false) => "bindings_9_0_1_x86_64_linux.rs",
        (Some(Mysql90), "x86" | "arm", "32", false) => "bindings_9_0_1_i686_linux.rs",
        (Some(Mysql90), "x86_64", "64", true) => "bindings_9_0_1_x86_64_windows.rs",
        (Some(Mysql90), "x86", "32", true) => "bindings_9_0_1_i686_windows.rs",
        (Some(MariaDb10), "x86_64" | "aarch64", "64", false) => {
            "bindings_mariadb_10_11_x86_64_linux.rs"
        }
        (Some(MariaDb10), "x86", "32", false) => "bindings_mariadb_10_11_i686_linux.rs",
        (Some(MariaDb10), "arm", "32", false) => "bindings_mariadb_10_11_armv6_linux.rs",
        (Some(MariaDb10), "x86_64", "64", true) => "bindings_mariadb_10_11_x86_64_windows.rs",
        (Some(MariaDb10), "x86", "32", true) => "bindings_mariadb_10_11_i686_windows.rs",
        _ => {
            panic!(
                "mysqlclient-sys does not provide bundled bindings for libmysqlclient `{version_str}` \
                 for the target `{}`.
                 Consider using the `buildtime_bindgen` feature or \
                 contribute bindings to the crate\n\
                 Debug information: (version: {version:?}, target_arch: {target_arch}, ptr_size: {ptr_size})",
                std::env::var("TARGET").expect("Set by cargo")
            )
        }
    };

    let root = std::env::var("CARGO_MANIFEST_DIR").expect("Set by cargo");
    let mut bindings = PathBuf::from(root);
    bindings.push("bindings");
    bindings.push(bindings_path);
    std::fs::copy(bindings, bindings_target).unwrap();
}

#[cfg(target_env = "msvc")]
fn try_vcpkg() -> bool {
    if vcpkg::find_package(VCPKG_MYSQL_LIB).is_ok() {
        return true;
    } else if vcpkg::find_package(VCPKG_MARIADB_LIB).is_ok() {
        return true;
    }
    false
}

#[cfg(not(target_env = "msvc"))]
fn try_vcpkg() -> bool {
    false
}

#[cfg(not(feature = "buildtime_bindgen"))]
fn autogen_bindings(_target: &str) {}

#[cfg(feature = "buildtime_bindgen")]
fn autogen_bindings(target: &str) {
    // if you update the options here you also need to
    // update the bindgen command in `DEVELOPMENT.md`
    // and regenerate the bundled bindings with the new options
    let mut builder = bindgen::Builder::default()
        // The input header we would like to generate
        // bindings for.
        .header("bindings/wrapper.h")
        .allowlist_function("mysql.*")
        .allowlist_function("mariadb.*")
        .allowlist_type("MYSQL.*")
        .allowlist_type("MARIADB.*")
        .allowlist_type("mysql.*")
        .allowlist_type("mariadb.*")
        .allowlist_var("MYSQL.*")
        .allowlist_var("MARIADB.*")
        .default_enum_style(bindgen::EnumVariation::Rust {
            non_exhaustive: true,
        })
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

    if let Ok(lib) = pkg_config::probe_library(PKG_CONFIG_MYSQL_LIB)
        .or_else(|_| pkg_config::probe_library(PKG_CONFIG_MARIADB_LIB))
    {
        for include in lib.include_paths {
            builder = builder.clang_arg(format!("-I{}", include.display()));
        }
    } else if let Ok(path) = env::var("MYSQLCLIENT_INCLUDE_DIR")
        .or_else(|_| env::var(format!("MYSQLCLIENT_INCLUDE_DIR_{target}")))
    {
        builder = builder.clang_arg(format!("-I{path}"));
    } else if let Some(include) = mysql_config_variable("--include") {
        builder = builder.clang_arg(include);
    } else {
        #[cfg(target_env = "msvc")]
        if let Ok(lib) =
            vcpkg::find_package(VCPKG_MYSQL_LIB).or_else(|_| vcpkg::find_package(VCPKG_MARIADB_LIB))
        {
            for include in lib.include_paths {
                builder = builder.clang_arg(format!("-I{}\\mysql", include.display()));
            }
        }
    }

    let bindings = builder
        // Finish the builder and generate the bindings.
        .generate()
        // Unwrap the Result and panic on failure.
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = std::path::PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}