diff --git a/crates/libduckdb-sys/Cargo.toml b/crates/libduckdb-sys/Cargo.toml index cade845b..5ce642ce 100644 --- a/crates/libduckdb-sys/Cargo.toml +++ b/crates/libduckdb-sys/Cargo.toml @@ -8,6 +8,12 @@ homepage = { workspace = true } keywords = { workspace = true } readme = { workspace = true } build = "build.rs" +# `links = "duckdb"` lets the build script publish metadata +# (`cargo:include`, `cargo:lib_dir`) that downstream crates can read +# via `DEP_DUCKDB_*` environment variables in their own build scripts. +# Required for crates that need to compile their own C/C++ code that +# `#include "duckdb.hpp"` against the bundled headers. +links = "duckdb" categories = ["external-ffi-bindings", "database"] description = "Native bindings to the libduckdb library, C API" exclude = ["duckdb-sources"] diff --git a/crates/libduckdb-sys/build.rs b/crates/libduckdb-sys/build.rs index 60f263d6..9e2853d5 100644 --- a/crates/libduckdb-sys/build.rs +++ b/crates/libduckdb-sys/build.rs @@ -120,6 +120,16 @@ mod build_linked { #[allow(unused_variables)] let header = find_duckdb(out_dir); + // Publish the resolved include directory so downstream crates + // that compile their own C/C++ code can read it from + // `DEP_DUCKDB_INCLUDE`. Wrapper-only mode (no explicit dir + // from env / vcpkg / pkg-config / download) intentionally + // skips the emission — there's no single directory to point + // at; the system header is on the default search path already. + if let Some(include_dir) = include_dir_for(&header) { + println!("cargo:include={include_dir}"); + } + #[cfg(not(feature = "buildtime_bindgen"))] { std::fs::copy( @@ -138,6 +148,21 @@ mod build_linked { } } + /// The `HeaderLocation` enum string conversion appends a filename to + /// the path it resolved. For `cargo:include=...` we want the + /// directory itself, not a file path. Re-derive it. + fn include_dir_for(header: &HeaderLocation) -> Option { + match header { + HeaderLocation::FromEnvironment => { + env::var("DUCKDB_INCLUDE_DIR") + .or_else(|_| env::var("DUCKDB_LIB_DIR")) + .ok() + } + HeaderLocation::FromPath(path) => Some(path.clone()), + HeaderLocation::Wrapper => None, + } + } + fn link_directive() -> &'static str { // If the user specifies DUCKDB_STATIC, do static // linking, unless it's explicitly set to 0. diff --git a/crates/libduckdb-sys/build_bundled_cc.rs b/crates/libduckdb-sys/build_bundled_cc.rs index 364397f7..c420ad68 100644 --- a/crates/libduckdb-sys/build_bundled_cc.rs +++ b/crates/libduckdb-sys/build_bundled_cc.rs @@ -116,7 +116,15 @@ fn untar_archive(out_dir: &str) { 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 include_path = Path::new(out_dir).join("duckdb/src/include"); + write_bindings(&include_path, out_path); + + // Publish the include directory so downstream crates that compile + // their own C/C++ code (e.g. extension shims that #include + // "duckdb.hpp") can pick it up from `DEP_DUCKDB_INCLUDE` without + // having to glob the target tree. Requires `links = "duckdb"` in + // the package manifest. + println!("cargo:include={}", include_path.display()); 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"); diff --git a/crates/libduckdb-sys/build_bundled_cmake.rs b/crates/libduckdb-sys/build_bundled_cmake.rs index 68a74c6a..1d9bc7ec 100644 --- a/crates/libduckdb-sys/build_bundled_cmake.rs +++ b/crates/libduckdb-sys/build_bundled_cmake.rs @@ -37,7 +37,14 @@ pub fn main(_out_dir: &str, out_path: &Path) { println!("cargo:rerun-if-env-changed=CMAKE_CXX_COMPILER_LAUNCHER"); println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); - write_bindings(&source_dir.join("src/include"), out_path); + let include_path = source_dir.join("src/include"); + write_bindings(&include_path, out_path); + + // Publish the include directory so downstream crates that compile + // their own C/C++ code can read it from `DEP_DUCKDB_INCLUDE`. + // See the matching emission in build_bundled_cc.rs. + println!("cargo:include={}", include_path.display()); + if let Some(configs) = env_var("DUCKDB_EXTENSION_CONFIGS") { if !configs.trim().is_empty() { panic!(