use crate::{win_target, write_bindings};
use std::{
collections::{HashMap, HashSet},
path::Path,
};
#[derive(serde::Deserialize)]
struct Sources {
cpp_files: HashSet<String>,
include_dirs: HashSet<String>,
}
#[derive(serde::Deserialize)]
struct Manifest {
base: Sources,
extensions: HashMap<String, Sources>,
}
fn add_extension(
manifest: &Manifest,
extension: &str,
cpp_files: &mut HashSet<String>,
include_dirs: &mut HashSet<String>,
) {
let sources = manifest.extensions.get(extension).unwrap_or_else(|| {
let mut available = manifest.extensions.keys().cloned().collect::<Vec<_>>();
available.sort();
panic!(
"extension `{extension}` is missing from duckdb manifest; available extensions: {}",
available.join(", ")
);
});
cpp_files.extend(sources.cpp_files.clone());
include_dirs.extend(sources.include_dirs.clone());
}
fn rewrite_generated_extension_loader(enabled_extensions: &[String], loader_path: &Path) -> std::io::Result<()> {
let template = include_str!("generated_extension_loader.cpp.in").replace('\r', "");
for placeholder in [
"${EXT_LOADER_BODY}",
"${EXT_NAME_VECTOR_INITIALIZER}",
"${EXT_TEST_PATH_INITIALIZER}",
] {
assert!(
template.contains(placeholder),
"generated extension loader template is missing placeholder {placeholder}; DuckDB upstream likely changed"
);
}
let mut extension_headers = String::new();
let mut extension_loader_body = String::new();
for ext in enabled_extensions {
extension_headers.push_str(&format!("#include \"{ext}_extension.hpp\"\n"));
extension_loader_body.push_str(&format!(
" if (extension==\"{ext}\") {{\n db.LoadStaticExtension<{}Extension>();\n return ExtensionLoadResult::LOADED_EXTENSION;\n }}\n",
to_camel_case(ext)
));
}
let linked_extensions = enabled_extensions
.iter()
.map(|ext| format!("\"{ext}\""))
.collect::<Vec<_>>()
.join(", ");
let loader_code = template
.replace("${EXT_LOADER_BODY}", &extension_loader_body)
.replace("${EXT_NAME_VECTOR_INITIALIZER}", &linked_extensions)
.replace("${EXT_TEST_PATH_INITIALIZER}", "");
let generated_banner = concat!(
"// Generated by crates/libduckdb-sys/build_bundled_cc.rs from\n",
"// crates/libduckdb-sys/generated_extension_loader.cpp.in.\n",
"// Do not edit this file directly.\n\n"
);
std::fs::write(
loader_path,
format!("{generated_banner}{extension_headers}{loader_code}"),
)
}
fn extension_enabled(extension: &str) -> bool {
extension == "core_functions"
|| (extension == "parquet" && cfg!(feature = "parquet"))
|| (extension == "json" && cfg!(feature = "json"))
}
fn to_camel_case(extension: &str) -> String {
extension
.split('_')
.filter(|part| !part.is_empty())
.map(|part| {
let (first, rest) = part.split_at(1);
format!("{}{}", first.to_ascii_uppercase(), rest)
})
.collect()
}
fn untar_archive(out_dir: &str) {
let path = "duckdb.tar.gz";
let tar_gz = std::fs::File::open(path).expect("archive file");
let tar = flate2::read::GzDecoder::new(tar_gz);
let mut archive = tar::Archive::new(tar);
archive.unpack(out_dir).expect("archive");
}
pub fn main(out_dir: &str, out_path: &Path) {
untar_archive(out_dir);
write_bindings(&Path::new(out_dir).join("duckdb/src/include"), out_path);
let manifest_file = std::fs::File::open(format!("{out_dir}/duckdb/manifest.json")).expect("manifest file");
let manifest: Manifest = serde_json::from_reader(manifest_file).expect("reading manifest file");
let mut cpp_files = HashSet::new();
let mut include_dirs = HashSet::new();
let mut extensions = manifest
.extensions
.keys()
.filter(|name| extension_enabled(name))
.cloned()
.collect::<Vec<String>>();
extensions.sort();
cpp_files.extend(manifest.base.cpp_files.clone());
include_dirs.extend(manifest.base.include_dirs.iter().cloned());
let mut cfg = cc::Build::new();
for extension in &extensions {
add_extension(&manifest, extension, &mut cpp_files, &mut include_dirs);
cfg.define(
&format!("DUCKDB_EXTENSION_{}_LINKED", extension.to_uppercase()),
Some("1"),
);
}
let loader_path = Path::new(out_dir).join("duckdb/generated_extension_loader_package_build.cpp");
rewrite_generated_extension_loader(&extensions, &loader_path)
.expect("failed to rewrite generated extension loader");
println!("cargo:warning=generated extension loader: {}", loader_path.display());
cfg.define("DUCKDB_EXTENSION_AUTOINSTALL_DEFAULT", "1");
cfg.define("DUCKDB_EXTENSION_AUTOLOAD_DEFAULT", "1");
println!("cargo:rerun-if-changed=duckdb.tar.gz");
cfg.include("duckdb");
cfg.includes(include_dirs.iter().map(|dir| format!("{out_dir}/duckdb/{dir}")));
let mut cpp_files_vec: Vec<String> = cpp_files.into_iter().collect();
cpp_files_vec.sort();
for f in cpp_files_vec.into_iter().map(|file| format!("{out_dir}/{file}")) {
cfg.file(f);
}
cfg.cpp(true)
.flag_if_supported("-std=c++11")
.flag_if_supported("/utf-8")
.flag_if_supported("/bigobj")
.warnings(false)
.flag_if_supported("-w");
let is_debug = match std::env::var("DEBUG") {
Ok(v) => v != "false" && v != "0",
Err(_) => false,
};
if !is_debug {
cfg.define("NDEBUG", None);
}
if win_target() {
cfg.define("DUCKDB_BUILD_LIBRARY", None);
}
cfg.compile("duckdb");
println!("cargo:lib_dir={out_dir}");
}