fn require_exists(path: &str) {
if !std::path::Path::new(path).exists() {
panic!(
"apache-datasketches-sys build.rs: expected file `{path}` does not exist.\n\n\
This file is listed in build.rs as part of a completed sketch family's bridge/shim \
file set, which is expected to be exhaustive. A missing file here means either a typo \
in the file list in build.rs, or the file was renamed, moved, or deleted without \
updating that list. Fix the list or restore the file."
);
}
}
fn main() {
let mut bridges: Vec<&str> = Vec::new();
if cfg!(feature = "hll") {
bridges.push("src/hll.rs");
}
if cfg!(feature = "theta") {
for path in [
"src/theta_sketch.rs",
"src/theta_compact.rs",
"src/theta_wrapped.rs",
"src/theta_union.rs",
"src/theta_intersection.rs",
"src/theta_a_not_b.rs",
"src/theta_jaccard.rs",
] {
require_exists(path);
bridges.push(path);
}
}
if cfg!(feature = "cpc") {
for path in ["src/cpc_sketch.rs", "src/cpc_union.rs"] {
require_exists(path);
bridges.push(path);
}
}
if cfg!(feature = "tuple") {
for path in [
"src/array_of_doubles_sketch.rs",
"src/array_of_doubles_compact.rs",
"src/array_of_doubles_union.rs",
"src/array_of_doubles_intersection.rs",
"src/array_of_doubles_a_not_b.rs",
"src/array_of_doubles_jaccard.rs",
"src/tuple_generic.rs",
"src/tuple_generic_union.rs",
"src/tuple_generic_intersection.rs",
"src/tuple_generic_a_not_b.rs",
"src/tuple_generic_jaccard.rs",
] {
require_exists(path);
bridges.push(path);
}
}
check_bridge_name_uniqueness(&bridges);
if bridges.is_empty() {
return;
}
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let vendor_dir = manifest_dir.join("vendor/datasketches-cpp");
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let generated_header_dir = out_dir
.join("cxxbridge/include")
.join(env!("CARGO_PKG_NAME"))
.join("src");
let mut build = cxx_build::bridges(&bridges);
build
.include(vendor_dir.join("common/include"))
.include(vendor_dir.join("hll/include"))
.include(vendor_dir.join("theta/include"))
.include(vendor_dir.join("cpc/include"))
.include(vendor_dir.join("tuple/include"))
.include("cpp")
.include("cpp/hll")
.include("cpp/theta")
.include("cpp/cpc")
.include("cpp/tuple")
.include(generated_header_dir)
.flag_if_supported("-std=c++17")
.flag_if_supported("-Wno-unnecessary-virtual-specifier");
if cfg!(feature = "hll") {
build
.file("cpp/hll/hll_sketch_shim.cc")
.file("cpp/hll/hll_union_shim.cc");
}
if cfg!(feature = "theta") {
for path in [
"cpp/theta/theta_sketch_shim.cc",
"cpp/theta/theta_compact_shim.cc",
"cpp/theta/theta_wrapped_shim.cc",
"cpp/theta/theta_union_shim.cc",
"cpp/theta/theta_intersection_shim.cc",
"cpp/theta/theta_a_not_b_shim.cc",
"cpp/theta/theta_jaccard_shim.cc",
] {
require_exists(path);
build.file(path);
}
}
if cfg!(feature = "cpc") {
for path in ["cpp/cpc/cpc_sketch_shim.cc", "cpp/cpc/cpc_union_shim.cc"] {
require_exists(path);
build.file(path);
}
}
if cfg!(feature = "tuple") {
for path in [
"cpp/tuple/array_of_doubles_sketch_shim.cc",
"cpp/tuple/array_of_doubles_compact_shim.cc",
"cpp/tuple/array_of_doubles_union_shim.cc",
"cpp/tuple/array_of_doubles_intersection_shim.cc",
"cpp/tuple/array_of_doubles_a_not_b_shim.cc",
"cpp/tuple/array_of_doubles_jaccard_shim.cc",
"cpp/tuple/dyn_summary.cc",
"cpp/tuple/tuple_generic_sketch_shim.cc",
"cpp/tuple/tuple_generic_compact_shim.cc",
"cpp/tuple/tuple_generic_union_shim.cc",
"cpp/tuple/tuple_generic_intersection_shim.cc",
"cpp/tuple/tuple_generic_a_not_b_shim.cc",
"cpp/tuple/tuple_generic_jaccard_shim.cc",
] {
require_exists(path);
build.file(path);
}
}
build.compile("apache_datasketches_sys");
println!("cargo:rerun-if-changed=src/hll.rs");
println!("cargo:rerun-if-changed=cpp/hll/hll_sketch_shim.h");
println!("cargo:rerun-if-changed=cpp/hll/hll_sketch_shim.cc");
println!("cargo:rerun-if-changed=cpp/hll/hll_union_shim.h");
println!("cargo:rerun-if-changed=cpp/hll/hll_union_shim.cc");
println!("cargo:rerun-if-changed=src/theta_sketch.rs");
println!("cargo:rerun-if-changed=src/theta_compact.rs");
println!("cargo:rerun-if-changed=src/theta_wrapped.rs");
println!("cargo:rerun-if-changed=src/theta_union.rs");
println!("cargo:rerun-if-changed=src/theta_intersection.rs");
println!("cargo:rerun-if-changed=src/theta_a_not_b.rs");
println!("cargo:rerun-if-changed=src/theta_jaccard.rs");
println!("cargo:rerun-if-changed=cpp/theta/theta_sketch_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_sketch_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_compact_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_compact_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_wrapped_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_wrapped_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_union_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_union_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_intersection_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_intersection_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_a_not_b_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_a_not_b_shim.cc");
println!("cargo:rerun-if-changed=cpp/theta/theta_jaccard_shim.h");
println!("cargo:rerun-if-changed=cpp/theta/theta_jaccard_shim.cc");
println!("cargo:rerun-if-changed=src/cpc_sketch.rs");
println!("cargo:rerun-if-changed=src/cpc_union.rs");
println!("cargo:rerun-if-changed=cpp/cpc/cpc_sketch_shim.h");
println!("cargo:rerun-if-changed=cpp/cpc/cpc_sketch_shim.cc");
println!("cargo:rerun-if-changed=cpp/cpc/cpc_union_shim.h");
println!("cargo:rerun-if-changed=cpp/cpc/cpc_union_shim.cc");
println!("cargo:rerun-if-changed=src/array_of_doubles_sketch.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_compact.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_input.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_union.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_intersection.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_a_not_b.rs");
println!("cargo:rerun-if-changed=src/array_of_doubles_jaccard.rs");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_sketch_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_sketch_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_compact_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_compact_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_union_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_union_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_intersection_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_intersection_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_a_not_b_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_a_not_b_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_jaccard_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/array_of_doubles_jaccard_shim.cc");
println!("cargo:rerun-if-changed=src/tuple_generic.rs");
println!("cargo:rerun-if-changed=cpp/tuple/dyn_summary.h");
println!("cargo:rerun-if-changed=cpp/tuple/dyn_summary.cc");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_sketch_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_sketch_shim.cc");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_compact_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_compact_shim.cc");
println!("cargo:rerun-if-changed=src/tuple_generic_input.rs");
println!("cargo:rerun-if-changed=src/tuple_generic_union.rs");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_union_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_union_shim.cc");
println!("cargo:rerun-if-changed=src/tuple_generic_intersection.rs");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_intersection_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_intersection_shim.cc");
println!("cargo:rerun-if-changed=src/tuple_generic_a_not_b.rs");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_a_not_b_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_a_not_b_shim.cc");
println!("cargo:rerun-if-changed=src/tuple_generic_jaccard.rs");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_jaccard_shim.h");
println!("cargo:rerun-if-changed=cpp/tuple/tuple_generic_jaccard_shim.cc");
}
fn check_bridge_name_uniqueness(bridges: &[&str]) {
use std::collections::HashMap;
let mut type_defs: HashMap<String, String> = HashMap::new();
let mut fn_defs: HashMap<String, String> = HashMap::new();
for &path in bridges {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
};
let lines: Vec<&str> = content.lines().collect();
let mut i = 0;
let recorded_before = type_defs.len() + fn_defs.len();
let mut brace_balance: i32 = 0;
let mut saw_bridge_attr = false;
let mut bridge_entry_depth: Option<i32> = None;
while i < lines.len() {
let trimmed = lines[i].trim();
if trimmed.starts_with("//") {
i += 1;
continue;
}
if trimmed.starts_with("#[cxx::bridge") {
saw_bridge_attr = true;
i += 1;
continue;
}
if saw_bridge_attr || bridge_entry_depth.is_some() {
let opens = trimmed.matches('{').count() as i32;
let closes = trimmed.matches('}').count() as i32;
if saw_bridge_attr && opens > 0 {
bridge_entry_depth = Some(brace_balance);
saw_bridge_attr = false;
}
brace_balance += opens - closes;
if bridge_entry_depth == Some(brace_balance) {
bridge_entry_depth = None;
}
}
if bridge_entry_depth.is_none() {
i += 1;
continue;
}
if let Some(name) = extract_struct_or_enum_name(trimmed) {
record_definition(&mut type_defs, name, path, "shared struct/enum");
i += 1;
continue;
}
if let Some(rest) = trimmed.strip_prefix("type ") {
let rest = rest.trim_end_matches(';').trim();
if !rest.contains('=') {
if !rest.is_empty() {
record_definition(&mut type_defs, rest.to_string(), path, "opaque type");
}
}
i += 1;
continue;
}
if trimmed.starts_with("fn ") {
let mut stmt = String::new();
let mut paren_depth = 0i32;
let mut seen_paren = false;
let mut j = i;
loop {
let line = lines[j];
stmt.push_str(line);
stmt.push(' ');
for c in line.chars() {
match c {
'(' => {
paren_depth += 1;
seen_paren = true;
}
')' => paren_depth -= 1,
_ => {}
}
}
if seen_paren && paren_depth == 0 {
let end = stmt.trim_end();
if end.ends_with(';') || end.ends_with('{') || end.ends_with('}') {
break;
}
}
j += 1;
if j >= lines.len() {
break;
}
}
if j > i {
for line in &lines[i + 1..=j.min(lines.len() - 1)] {
brace_balance += line.matches('{').count() as i32;
brace_balance -= line.matches('}').count() as i32;
}
if bridge_entry_depth == Some(brace_balance) {
bridge_entry_depth = None;
}
}
i = j + 1;
if stmt.trim_end().ends_with(';') {
if let (Some(fn_pos), Some(paren_pos)) = (stmt.find("fn "), stmt.find('(')) {
if paren_pos > fn_pos {
let name = stmt[fn_pos + 3..paren_pos].trim().to_string();
let params = stmt[paren_pos + 1..].trim_start();
let is_method = params.starts_with("self");
if !is_method && !name.is_empty() {
record_definition(&mut fn_defs, name, path, "free function");
}
}
}
}
continue;
}
i += 1;
}
assert!(
type_defs.len() + fn_defs.len() > recorded_before,
"apache-datasketches-sys build.rs: the bridge-name scanner recorded no type \
or free function from `{path}`.\n\n\
Every `#[cxx::bridge]` file declares at least one opaque type, shared \
struct/enum, or free function, so this means the scan failed to find or \
follow the bridge module in that file -- most likely the brace bookkeeping \
in check_bridge_name_uniqueness was thrown off (a brace inside a block \
comment or string literal), or the `#[cxx::bridge]`/`mod` shape changed. \
The cross-bridge name-collision check is therefore NOT covering this file, \
which previously caused a SIGBUS. Fix the scanner (or the file) rather than \
removing this assertion."
);
}
}
fn extract_struct_or_enum_name(trimmed: &str) -> Option<String> {
let after_kw = trimmed
.strip_prefix("pub struct ")
.or_else(|| trimmed.strip_prefix("struct "))
.or_else(|| trimmed.strip_prefix("pub enum "))
.or_else(|| trimmed.strip_prefix("enum "))?;
let name: String = after_kw
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if name.is_empty() {
None
} else {
Some(name)
}
}
fn record_definition(
map: &mut std::collections::HashMap<String, String>,
name: String,
path: &str,
kind: &str,
) {
if let Some(existing) = map.get(&name) {
panic!(
"apache-datasketches-sys build.rs: duplicate {kind} name `{name}` is defined in both \
`{existing}` and `{path}`.\n\n\
cxx derives each generated extern \"C\" trampoline symbol (for free functions) and \
each generated C++ type definition (for shared struct/enum/opaque types) from the \
bridge namespace plus the item's name alone -- not from parameter types and not from \
which bridge module declared it. Two bridges declaring the same name therefore emit \
the identical C++ symbol, and the linker silently picks one definition for both call \
sites: callers of the \"losing\" declaration get the wrong shim type reinterpreted at \
runtime, which shows up as a crash or a wrong result, not a link error. This is \
exactly the bug class that previously caused a SIGBUS when the theta and tuple \
jaccard shims both declared `jaccard_sketch_sketch` under --all-features. Rename one \
of the two, typically by prefixing with its family name (e.g. `tuple_jaccard_*`, \
`TupleResizeFactor`)."
);
}
map.insert(name, path.to_string());
}