use std::env;
use std::path::Path;
#[allow(dead_code)]
fn win_target() -> bool {
std::env::var("CARGO_CFG_WINDOWS").is_ok()
}
#[allow(dead_code)]
fn is_compiler(compiler_name: &str) -> bool {
std::env::var("CARGO_CFG_TARGET_ENV").map_or(false, |v| v == compiler_name)
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let out_path = Path::new(&out_dir).join("bindgen.rs");
#[cfg(feature = "bundled")]
{
build_bundled::main(&out_dir, &out_path);
}
#[cfg(not(feature = "bundled"))]
{
build_linked::main(&out_dir, &out_path)
}
}
#[cfg(feature = "bundled")]
mod build_bundled {
use std::path::Path;
pub fn main(out_dir: &str, out_path: &Path) {
let lib_name = super::lib_name();
if !cfg!(feature = "bundled") {
panic!("This module should not be used: bundled feature has not been enabled");
}
#[cfg(feature = "buildtime_bindgen")]
{
use super::{bindings, HeaderLocation};
let header = HeaderLocation::FromPath(format!("{}/duckdb.h", lib_name));
bindings::write_to_out_dir(header, out_path);
}
#[cfg(not(feature = "buildtime_bindgen"))]
{
use std::fs;
fs::copy(format!("{}/bindgen_bundled_version.rs", lib_name), out_path)
.expect("Could not copy bindings to output directory");
}
println!("cargo:rerun-if-changed={}/duckdb.hpp", lib_name);
println!("cargo:rerun-if-changed={}/duckdb.cpp", lib_name);
let mut cfg = cc::Build::new();
cfg.file(format!("{}/duckdb.cpp", lib_name))
.cpp(true)
.shared_flag(true)
.pic(true)
.flag_if_supported("-std=c++11")
.flag_if_supported("-stdlib=libc++")
.flag_if_supported("-stdlib=libstdc++")
.warnings(false);
cfg.compile(lib_name);
println!("cargo:lib_dir={}", out_dir);
}
}
fn env_prefix() -> &'static str {
"DUCKDB"
}
fn lib_name() -> &'static str {
"duckdb"
}
pub enum HeaderLocation {
FromEnvironment,
Wrapper,
FromPath(String),
}
impl From<HeaderLocation> for String {
fn from(header: HeaderLocation) -> String {
match header {
HeaderLocation::FromEnvironment => {
let prefix = env_prefix();
let mut header = env::var(format!("{}_INCLUDE_DIR", prefix))
.unwrap_or_else(|_| env::var(format!("{}_LIB_DIR", env_prefix())).unwrap());
header.push_str("/duckdb.h");
header
}
HeaderLocation::Wrapper => "wrapper.h".into(),
HeaderLocation::FromPath(path) => path,
}
}
}
#[cfg(not(feature = "bundled"))]
mod build_linked {
#[cfg(feature = "vcpkg")]
extern crate vcpkg;
#[cfg(feature = "buildtime_bindgen")]
use super::bindings;
use super::{env_prefix, is_compiler, lib_name, win_target, HeaderLocation};
use std::env;
use std::path::Path;
pub fn main(_out_dir: &str, out_path: &Path) {
#[allow(unused_variables)]
let header = find_duckdb();
if !cfg!(feature = "buildtime_bindgen") {
std::fs::copy(format!("{}/bindgen_bundled_version.rs", lib_name()), out_path)
.expect("Could not copy bindings to output directory");
} else {
#[cfg(feature = "buildtime_bindgen")]
{
bindings::write_to_out_dir(header, out_path);
}
}
}
fn find_link_mode() -> &'static str {
match &env::var(format!("{}_STATIC", env_prefix())) {
Ok(v) if v != "0" => "static",
_ => "dylib",
}
}
fn find_duckdb() -> HeaderLocation {
let link_lib = lib_name();
println!("cargo:rerun-if-env-changed={}_INCLUDE_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_LIB_DIR", env_prefix());
println!("cargo:rerun-if-env-changed={}_STATIC", env_prefix());
if cfg!(feature = "vcpkg") && is_compiler("msvc") {
println!("cargo:rerun-if-env-changed=VCPKGRS_DYNAMIC");
}
println!("cargo:link-target={}", link_lib);
if win_target() && cfg!(feature = "winduckdb") {
println!("cargo:rustc-link-lib=dylib={}", link_lib);
return HeaderLocation::Wrapper;
}
if let Ok(dir) = env::var(format!("{}_LIB_DIR", env_prefix())) {
println!("cargo:rustc-env=LD_LIBRARY_PATH={}", dir);
let pkgconfig_path = Path::new(&dir).join("pkgconfig");
env::set_var("PKG_CONFIG_PATH", pkgconfig_path);
if pkg_config::Config::new().probe(link_lib).is_err() {
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
println!("cargo:rustc-link-search={}", dir);
}
return HeaderLocation::FromEnvironment;
}
if let Some(header) = try_vcpkg() {
return header;
}
match pkg_config::Config::new().print_system_libs(false).probe(link_lib) {
Ok(mut lib) => {
if let Some(mut header) = lib.include_paths.pop() {
header.push("duckdb.h");
HeaderLocation::FromPath(header.to_string_lossy().into())
} else {
HeaderLocation::Wrapper
}
}
Err(_) => {
println!("cargo:rustc-link-lib={}={}", find_link_mode(), link_lib);
HeaderLocation::Wrapper
}
}
}
fn try_vcpkg() -> Option<HeaderLocation> {
if cfg!(feature = "vcpkg") && is_compiler("msvc") {
if let Ok(mut lib) = vcpkg::Config::new().probe(lib_name()) {
if let Some(mut header) = lib.include_paths.pop() {
header.push("duckdb.h");
return Some(HeaderLocation::FromPath(header.to_string_lossy().into()));
}
}
None
} else {
None
}
}
}
#[cfg(feature = "buildtime_bindgen")]
mod bindings {
use super::HeaderLocation;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
pub fn write_to_out_dir(header: HeaderLocation, out_path: &Path) {
let header: String = header.into();
let mut output = Vec::new();
bindgen::builder()
.trust_clang_mangling(false)
.header(header.clone())
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.rustfmt_bindings(true)
.generate()
.unwrap_or_else(|_| panic!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))
.expect("could not write output of bindgen");
let output = String::from_utf8(output).expect("bindgen output was not UTF-8?!");
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(out_path)
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
file.write_all(output.as_bytes())
.unwrap_or_else(|_| panic!("Could not write to {:?}", out_path));
}
}